• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 import org.gradle.api.*
2 import org.gradle.api.attributes.*
3 import org.gradle.api.file.*
4 import org.gradle.api.tasks.*
5 import org.gradle.api.tasks.bundling.*
6 import org.gradle.api.tasks.compile.*
7 import org.gradle.jvm.toolchain.*
8 import org.gradle.kotlin.dsl.*
9 import org.gradle.work.*
10 import org.jetbrains.kotlin.gradle.dsl.*
11 
12 /**
13  * This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor.
14  * The source file for the module descriptor is expected at <project-dir>/src/module-info.java.
15  *
16  * To maintain backwards compatibility with Java 8, the jvm JAR is marked as a multi-release JAR
17  * with the module-info.class being moved to META-INF/versions/9/module-info.class.
18  *
19  * The Java toolchains feature of Gradle is used to detect or provision a JDK 11,
20  * which is used to compile the module descriptor.
21  */
22 object Java9Modularity {
23 
24     /**
25      * Task that patches `module-info.java` and removes `requires kotlinx.atomicfu` directive.
26      *
27      * To have JPMS properly supported, Kotlin compiler **must** be supplied with the correct `module-info.java`.
28      * The correct module info has to contain `atomicfu` requirement because atomicfu plugin kicks-in **after**
29      * the compilation process. But `atomicfu` is compile-only dependency that shouldn't be present in the final
30      * `module-info.java` and that's exactly what this task ensures.
31      */
32     abstract class ProcessModuleInfoFile : DefaultTask() {
33         @get:InputFile
34         @get:NormalizeLineEndings
35         abstract val moduleInfoFile: RegularFileProperty
36 
37         @get:OutputFile
38         abstract val processedModuleInfoFile: RegularFileProperty
39 
40         private val projectPath = project.path
41 
42         @TaskAction
43         fun process() {
44             val sourceFile = moduleInfoFile.get().asFile
45             if (!sourceFile.exists()) {
46                 throw IllegalStateException("$sourceFile not found in $projectPath")
47             }
48             val outputFile = processedModuleInfoFile.get().asFile
49             sourceFile.useLines { lines ->
50                 outputFile.outputStream().bufferedWriter().use { writer ->
51                     for (line in lines) {
52                         if ("kotlinx.atomicfu" in line) continue
53                         writer.write(line)
54                         writer.newLine()
55                     }
56                 }
57             }
58         }
59     }
60 
61     @JvmStatic
62     fun configure(project: Project) = with(project) {
63         val javaToolchains = extensions.findByType(JavaToolchainService::class.java)
64             ?: error("Gradle JavaToolchainService is not available")
65         val target = when (val kotlin = extensions.getByName("kotlin")) {
66             is KotlinJvmProjectExtension -> kotlin.target
67             is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm")
68             else -> throw IllegalStateException("Unknown Kotlin project extension in $project")
69         }
70         val compilation = target.compilations.getByName("main")
71 
72         // Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up.
73         // For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772
74         configurations.getByName(compilation.compileDependencyConfigurationName).attributes {
75             attribute(
76                 LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
77                 objects.named(LibraryElements::class, LibraryElements.JAR)
78             )
79         }
80 
81         val processModuleInfoFile by tasks.registering(ProcessModuleInfoFile::class) {
82             moduleInfoFile = file("${target.name.ifEmpty { "." }}/src/module-info.java")
83             processedModuleInfoFile = project.layout.buildDirectory.file("generated-sources/module-info-processor/module-info.java")
84         }
85 
86         val compileJavaModuleInfo = tasks.register("compileModuleInfoJava", JavaCompile::class.java) {
87             val moduleName = project.name.replace('-', '.') // this module's name
88             val compileKotlinTask =
89                 compilation.compileTaskProvider.get() as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile
90                     ?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}")
91             val targetDir = compileKotlinTask.destinationDirectory.dir("../java9")
92 
93             // Use a Java 11 compiler for the module-info.
94             javaCompiler = javaToolchains.compilerFor {
95                 languageVersion = JavaLanguageVersion.of(11)
96             }
97 
98             // Always compile kotlin classes before the module descriptor.
99             dependsOn(compileKotlinTask)
100 
101             // Add the module-info source file.
102             // Note that we use the parent dir and an include filter,
103             // this is needed for Gradle's module detection to work in
104             // org.gradle.api.tasks.compile.JavaCompile.createSpec
105             source(processModuleInfoFile.map { it.processedModuleInfoFile.asFile.get().parentFile })
106             val generatedModuleInfoFile = processModuleInfoFile.flatMap { it.processedModuleInfoFile.asFile }
107             include { it.file == generatedModuleInfoFile.get() }
108 
109             // Set the task outputs and destination directory
110             outputs.dir(targetDir)
111             destinationDirectory = targetDir
112 
113             // Configure JVM compatibility
114             sourceCompatibility = JavaVersion.VERSION_1_9.toString()
115             targetCompatibility = JavaVersion.VERSION_1_9.toString()
116 
117             // Set the Java release version.
118             options.release = 9
119 
120             // Ignore warnings about using 'requires transitive' on automatic modules.
121             // not needed when compiling with recent JDKs, e.g. 17
122             options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
123 
124             // Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
125             val destinationDirProperty = compileKotlinTask.destinationDirectory.asFile
126             options.compilerArgumentProviders.add {
127                 val kotlinCompileDestinationDir = destinationDirProperty.get()
128                 listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir")
129             }
130 
131             // Use the classpath of the compileKotlinJvm task.
132             // Also ensure that the module path is used instead of classpath.
133             classpath = compileKotlinTask.libraries
134             modularity.inferModulePath = true
135         }
136 
137         tasks.named<Jar>(target.artifactsTaskName) {
138             manifest {
139                 attributes("Multi-Release" to true)
140             }
141             from(compileJavaModuleInfo) {
142                 // Include **only** file we are interested in as JavaCompile output also contains some tmp files
143                 include("module-info.class")
144                 into("META-INF/versions/9/")
145             }
146         }
147     }
148 }
149