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