|
| 1 | +/* |
| 2 | + * 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.provider.* |
| 9 | +import org.gradle.api.specs.* |
| 10 | +import org.gradle.api.tasks.bundling.* |
| 11 | +import org.gradle.api.tasks.compile.* |
| 12 | +import org.gradle.jvm.toolchain.* |
| 13 | +import org.gradle.kotlin.dsl.* |
| 14 | +import org.gradle.api.logging.Logger |
| 15 | +import org.jetbrains.kotlin.gradle.dsl.* |
| 16 | +import java.io.* |
| 17 | + |
| 18 | +/** |
| 19 | + * This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor. |
| 20 | + * The source file for the module descriptor is expected at <project-dir>/src/module-info.java. |
| 21 | + * |
| 22 | + * To maintain backwards compatibility with Java 8, the jvm JAR is marked as a multi-release JAR |
| 23 | + * with the module-info.class being moved to META-INF/versions/9/module-info.class. |
| 24 | + * |
| 25 | + * The Java toolchains feature of Gradle is used to detect or provision a JDK 11, |
| 26 | + * which is used to compile the module descriptor. |
| 27 | + */ |
| 28 | +object Java9Modularity { |
| 29 | + |
| 30 | + private class ModuleInfoFilter( |
| 31 | + private val compileKotlinTaskPath: String, |
| 32 | + private val javaVersionProvider: Provider<JavaVersion>, |
| 33 | + private val moduleInfoFile: File, |
| 34 | + private val logger: Logger |
| 35 | + ) : Spec<FileTreeElement> { |
| 36 | + private val isJava9Compatible |
| 37 | + get() = javaVersionProvider.orNull?.isJava9Compatible == true |
| 38 | + private var logged = false |
| 39 | + |
| 40 | + private fun logStatusOnce() { |
| 41 | + if (logged) return |
| 42 | + if (isJava9Compatible) { |
| 43 | + logger.info("Module-info checking is enabled; $compileKotlinTaskPath is compiled using Java ${javaVersionProvider.get()}") |
| 44 | + } else { |
| 45 | + logger.info("Module-info checking is disabled") |
| 46 | + } |
| 47 | + logged = true |
| 48 | + } |
| 49 | + |
| 50 | + override fun isSatisfiedBy(element: FileTreeElement): Boolean { |
| 51 | + logStatusOnce() |
| 52 | + if (isJava9Compatible) return false |
| 53 | + return element.file == moduleInfoFile |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + @JvmStatic |
| 58 | + fun configure(project: Project) = with(project) { |
| 59 | + val javaToolchains = extensions.findByType(JavaToolchainService::class.java) |
| 60 | + ?: error("Gradle JavaToolchainService is not available") |
| 61 | + val target = when (val kotlin = extensions.getByName("kotlin")) { |
| 62 | + is KotlinJvmProjectExtension -> kotlin.target |
| 63 | + is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm") |
| 64 | + else -> throw IllegalStateException("Unknown Kotlin project extension in $project") |
| 65 | + } |
| 66 | + val compilation = target.compilations.getByName("main") |
| 67 | + |
| 68 | + // Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up. |
| 69 | + // For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772 |
| 70 | + configurations.getByName(compilation.compileDependencyConfigurationName).attributes { |
| 71 | + attribute( |
| 72 | + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, |
| 73 | + objects.named(LibraryElements::class, LibraryElements.JAR) |
| 74 | + ) |
| 75 | + } |
| 76 | + |
| 77 | + val compileJavaModuleInfo = tasks.register("compileModuleInfoJava", JavaCompile::class.java) { |
| 78 | + val moduleName = project.name.replace('-', '.') // this module's name |
| 79 | + val sourceFile = file("${target.name.ifEmpty { "." }}/src/module-info.java") |
| 80 | + if (!sourceFile.exists()) { |
| 81 | + throw IllegalStateException("$sourceFile not found in $project") |
| 82 | + } |
| 83 | + val compileKotlinTask = |
| 84 | + compilation.compileTaskProvider.get() as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
| 85 | + ?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}") |
| 86 | + val targetDir = compileKotlinTask.destinationDirectory.dir("../java9") |
| 87 | + |
| 88 | + // Use a Java 11 compiler for the module-info. |
| 89 | + javaCompiler.set(javaToolchains.compilerFor { |
| 90 | + languageVersion.set(JavaLanguageVersion.of(11)) |
| 91 | + }) |
| 92 | + |
| 93 | + // Always compile kotlin classes before the module descriptor. |
| 94 | + dependsOn(compileKotlinTask) |
| 95 | + |
| 96 | + // Add the module-info source file. |
| 97 | + // Note that we use the parent dir and an include filter, |
| 98 | + // this is needed for Gradle's module detection to work in |
| 99 | + // org.gradle.api.tasks.compile.JavaCompile.createSpec |
| 100 | + source(sourceFile.parentFile) |
| 101 | + include { it.file == sourceFile } |
| 102 | + |
| 103 | + // The Kotlin compiler will parse and check module dependencies, |
| 104 | + // but it currently won't compile to a module-info.class file. |
| 105 | + // Note that module checking only works on JDK 9+, |
| 106 | + // because the JDK built-in base modules are not available in earlier versions. |
| 107 | + val javaVersionProvider = compileKotlinTask.kotlinJavaToolchain.javaVersion |
| 108 | + compileKotlinTask.exclude(ModuleInfoFilter(compileKotlinTask.path, javaVersionProvider, sourceFile, logger)) |
| 109 | + |
| 110 | + // Set the task outputs and destination directory |
| 111 | + outputs.dir(targetDir) |
| 112 | + destinationDirectory.set(targetDir) |
| 113 | + |
| 114 | + // Configure JVM compatibility |
| 115 | + sourceCompatibility = JavaVersion.VERSION_1_9.toString() |
| 116 | + targetCompatibility = JavaVersion.VERSION_1_9.toString() |
| 117 | + |
| 118 | + // Set the Java release version. |
| 119 | + options.release.set(9) |
| 120 | + |
| 121 | + // Ignore warnings about using 'requires transitive' on automatic modules. |
| 122 | + // not needed when compiling with recent JDKs, e.g. 17 |
| 123 | + options.compilerArgs.add("-Xlint:-requires-transitive-automatic") |
| 124 | + |
| 125 | + // Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly. |
| 126 | + val destinationDirProperty = compileKotlinTask.destinationDirectory.asFile |
| 127 | + options.compilerArgumentProviders.add { |
| 128 | + val kotlinCompileDestinationDir = destinationDirProperty.get() |
| 129 | + listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir") |
| 130 | + } |
| 131 | + |
| 132 | + // Use the classpath of the compileKotlinJvm task. |
| 133 | + // Also ensure that the module path is used instead of classpath. |
| 134 | + classpath = compileKotlinTask.libraries |
| 135 | + modularity.inferModulePath.set(true) |
| 136 | + } |
| 137 | + |
| 138 | + tasks.named<Jar>(target.artifactsTaskName) { |
| 139 | + manifest { |
| 140 | + attributes("Multi-Release" to true) |
| 141 | + } |
| 142 | + from(compileJavaModuleInfo) { |
| 143 | + into("META-INF/versions/9/") |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | +} |
0 commit comments