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