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