xref: /aosp_15_r20/external/kotlinx.serialization/buildSrc/src/main/kotlin/Java9Modularity.kt (revision 57b5a4a64c534cf7f27ac9427ceab07f3d8ed3d8)
1 /*
<lambda>null2  * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 import org.gradle.api.*
6 import org.gradle.api.file.*
7 import org.gradle.api.provider.*
8 import org.gradle.api.tasks.*
9 import org.gradle.api.tasks.bundling.*
10 import org.gradle.api.tasks.compile.*
11 import org.gradle.jvm.toolchain.*
12 import org.gradle.kotlin.dsl.*
13 import org.gradle.language.base.plugins.LifecycleBasePlugin.*
14 import org.gradle.process.*
15 import org.jetbrains.kotlin.gradle.*
16 import org.jetbrains.kotlin.gradle.dsl.*
17 import org.jetbrains.kotlin.gradle.plugin.*
18 import org.jetbrains.kotlin.gradle.plugin.mpp.*
19 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.*
20 import org.jetbrains.kotlin.gradle.targets.jvm.*
21 import org.jetbrains.kotlin.gradle.tasks.*
22 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
23 import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
24 import org.jetbrains.kotlin.tooling.core.*
25 import java.io.*
26 import kotlin.reflect.*
27 import kotlin.reflect.full.*
28 
29 object Java9Modularity {
30 
31     @JvmStatic
32     @JvmOverloads
33     fun Project.configureJava9ModuleInfo(multiRelease: Boolean = true) {
34         val disableJPMS = this.rootProject.extra.has("disableJPMS")
35         val ideaActive = System.getProperty("idea.active") == "true"
36         if (disableJPMS || ideaActive) return
37         val kotlin = extensions.findByType<KotlinProjectExtension>() ?: return
38         val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*, *> }
39         if (jvmTargets.isEmpty()) {
40             logger.warn("No Kotlin JVM targets found, can't configure compilation of module-info!")
41         }
42         jvmTargets.forEach { target ->
43             val artifactTask = tasks.getByName<Jar>(target.artifactsTaskName) {
44                 if (multiRelease) {
45                     manifest {
46                         attributes("Multi-Release" to true)
47                     }
48                 }
49             }
50 
51             target.compilations.forEach { compilation ->
52                 val compileKotlinTask = compilation.compileKotlinTask as KotlinCompile
53                 val defaultSourceSet = compilation.defaultSourceSet
54 
55                 // derive the names of the source set and compile module task
56                 val sourceSetName = defaultSourceSet.name + "Module"
57 
58                 kotlin.sourceSets.create(sourceSetName) {
59                     val sourceFile = this.kotlin.find { it.name == "module-info.java" }
60                     val targetDirectory = compileKotlinTask.destinationDirectory.map {
61                         it.dir("../${it.asFile.name}Module")
62                     }
63 
64                     // only configure the compilation if necessary
65                     if (sourceFile != null) {
66                         // register and wire a task to verify module-info.java content
67                         //
68                         // this will compile the whole sources again with a JPMS-aware target Java version,
69                         // so that the Kotlin compiler can do the necessary verifications
70                         // while compiling with `jdk-release=1.8` those verifications are not done
71                         //
72                         // this task is only going to be executed when running with `check` or explicitly,
73                         // not during normal build operations
74                         val verifyModuleTask = registerVerifyModuleTask(
75                             compileKotlinTask,
76                             sourceFile
77                         )
78                         tasks.named("check") {
79                             dependsOn(verifyModuleTask)
80                         }
81 
82                         // register a new compile module task
83                         val compileModuleTask = registerCompileModuleTask(
84                             compileKotlinTask,
85                             sourceFile,
86                             targetDirectory
87                         )
88 
89                         // add the resulting module descriptor to this target's artifact
90                         artifactTask.from(compileModuleTask.map { it.destinationDirectory }) {
91                             if (multiRelease) {
92                                 into("META-INF/versions/9/")
93                             }
94                         }
95                     } else {
96                         logger.info("No module-info.java file found in ${this.kotlin.srcDirs}, can't configure compilation of module-info!")
97                     }
98 
99                     // remove the source set to prevent Gradle warnings
100                     kotlin.sourceSets.remove(this)
101                 }
102             }
103         }
104     }
105 
106     /**
107      * Add a Kotlin compile task that compiles `module-info.java` source file and Kotlin sources together,
108      * the Kotlin compiler will parse and check module dependencies,
109      * but it currently won't compile to a module-info.class file.
110      */
111     private fun Project.registerVerifyModuleTask(
112         compileTask: KotlinCompile,
113         sourceFile: File
114     ): TaskProvider<out KotlinJvmCompile> {
115         apply<KotlinApiPlugin>()
116         val verifyModuleTaskName = "verify${compileTask.name.removePrefix("compile").capitalize()}Module"
117         // work-around for https://youtrack.jetbrains.com/issue/KT-60542
118         val kotlinApiPlugin = plugins.getPlugin(KotlinApiPlugin::class)
119         val verifyModuleTask = kotlinApiPlugin.registerKotlinJvmCompileTask(
120             verifyModuleTaskName,
121             compileTask.compilerOptions.moduleName.get()
122         )
123         verifyModuleTask {
124             group = VERIFICATION_GROUP
125             description = "Verify Kotlin sources for JPMS problems"
126             libraries.from(compileTask.libraries)
127             source(compileTask.sources)
128             source(compileTask.javaSources)
129             // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
130             @Suppress("INVISIBLE_MEMBER")
131             source(compileTask.scriptSources)
132             source(sourceFile)
133             destinationDirectory.set(temporaryDir)
134             multiPlatformEnabled.set(compileTask.multiPlatformEnabled)
135             compilerOptions {
136                 jvmTarget.set(JvmTarget.JVM_9)
137                 // To support LV override when set in aggregate builds
138                 languageVersion.set(compileTask.compilerOptions.languageVersion)
139                 freeCompilerArgs.addAll(
140                     listOf("-Xjdk-release=9",  "-Xsuppress-version-warnings", "-Xexpect-actual-classes")
141                 )
142                 optIn.addAll(compileTask.kotlinOptions.options.optIn)
143             }
144             // work-around for https://youtrack.jetbrains.com/issue/KT-60583
145             inputs.files(
146                 libraries.asFileTree.elements.map { libs ->
147                     libs
148                         .filter { it.asFile.exists() }
149                         .map {
150                             zipTree(it.asFile).filter { it.name == "module-info.class" }
151                         }
152                 }
153             ).withPropertyName("moduleInfosOfLibraries")
154             this as KotlinCompile
155             val kotlinPluginVersion = KotlinToolingVersion(kotlinApiPlugin.pluginVersion)
156             if (kotlinPluginVersion <= KotlinToolingVersion("1.9.255")) {
157                 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
158                 @Suppress("UNCHECKED_CAST")
159                 val ownModuleNameProp = (this::class.superclasses.first() as KClass<AbstractKotlinCompile<*>>)
160                     .declaredMemberProperties
161                     .find { it.name == "ownModuleName" }
162                     ?.get(this) as? Property<String>
163                 ownModuleNameProp?.set(compileTask.kotlinOptions.moduleName)
164             }
165 
166             val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT)
167             @OptIn(InternalKotlinGradlePluginApi::class)
168             if (taskKotlinLanguageVersion.get() < KotlinVersion.KOTLIN_2_0) {
169                 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
170                 @Suppress("INVISIBLE_MEMBER")
171                 commonSourceSet.from(compileTask.commonSourceSet)
172             } else {
173                 multiplatformStructure.refinesEdges.set(compileTask.multiplatformStructure.refinesEdges)
174                 multiplatformStructure.fragments.set(compileTask.multiplatformStructure.fragments)
175             }
176             // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
177             // and work-around for https://youtrack.jetbrains.com/issue/KT-60582
178             incremental = false
179         }
180         return verifyModuleTask
181     }
182 
183     private fun Project.registerCompileModuleTask(
184         compileTask: KotlinCompile,
185         sourceFile: File,
186         targetDirectory: Provider<out Directory>
187     ) = tasks.register("${compileTask.name}Module", JavaCompile::class) {
188         // Configure the module compile task.
189         source(sourceFile)
190         classpath = files()
191         destinationDirectory.set(targetDirectory)
192         // use a Java 11 toolchain with release 9 option
193         // because for some OS / architecture combinations
194         // there are no Java 9 builds available
195         javaCompiler.set(
196             this@registerCompileModuleTask.the<JavaToolchainService>().compilerFor {
197                 languageVersion.set(JavaLanguageVersion.of(11))
198             }
199         )
200         options.release.set(9)
201 
202         options.compilerArgumentProviders.add(object : CommandLineArgumentProvider {
203             @get:CompileClasspath
204             val compileClasspath = compileTask.libraries
205 
206             @get:CompileClasspath
207             val compiledClasses = compileTask.destinationDirectory
208 
209             @get:Input
210             val moduleName = sourceFile
211                 .readLines()
212                 .single { it.contains("module ") }
213                 .substringAfter("module ")
214                 .substringBefore(' ')
215                 .trim()
216 
217             override fun asArguments() = mutableListOf(
218                 // Provide the module path to the compiler instead of using a classpath.
219                 // The module path should be the same as the classpath of the compiler.
220                 "--module-path",
221                 compileClasspath.asPath,
222                 "--patch-module",
223                 "$moduleName=${compiledClasses.get()}",
224                 "-Xlint:-requires-transitive-automatic"
225             )
226         })
227     }
228 }
229