diff options
Diffstat (limited to 'buildSrc/src/main/kotlin/Java9Modularity.kt')
-rw-r--r-- | buildSrc/src/main/kotlin/Java9Modularity.kt | 208 |
1 files changed, 167 insertions, 41 deletions
diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt index 05052972..2743b00f 100644 --- a/buildSrc/src/main/kotlin/Java9Modularity.kt +++ b/buildSrc/src/main/kotlin/Java9Modularity.kt @@ -1,20 +1,41 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + import org.gradle.api.* +import org.gradle.api.file.* +import org.gradle.api.provider.* +import org.gradle.api.tasks.* import org.gradle.api.tasks.bundling.* import org.gradle.api.tasks.compile.* +import org.gradle.jvm.toolchain.* import org.gradle.kotlin.dsl.* +import org.gradle.language.base.plugins.LifecycleBasePlugin.* +import org.gradle.process.* +import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.* +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.* import org.jetbrains.kotlin.gradle.targets.jvm.* +import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile +import org.jetbrains.kotlin.tooling.core.* import java.io.* +import kotlin.reflect.* +import kotlin.reflect.full.* object Java9Modularity { @JvmStatic @JvmOverloads fun Project.configureJava9ModuleInfo(multiRelease: Boolean = true) { + val disableJPMS = this.rootProject.extra.has("disableJPMS") + val ideaActive = System.getProperty("idea.active") == "true" + if (disableJPMS || ideaActive) return val kotlin = extensions.findByType<KotlinProjectExtension>() ?: return - val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*> } + val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*, *> } if (jvmTargets.isEmpty()) { logger.warn("No Kotlin JVM targets found, can't configure compilation of module-info!") } @@ -28,75 +49,180 @@ object Java9Modularity { } target.compilations.forEach { compilation -> - val compileKotlinTask = compilation.compileKotlinTask as AbstractCompile + val compileKotlinTask = compilation.compileKotlinTask as KotlinCompile val defaultSourceSet = compilation.defaultSourceSet // derive the names of the source set and compile module task val sourceSetName = defaultSourceSet.name + "Module" - val compileModuleTaskName = compileKotlinTask.name + "Module" kotlin.sourceSets.create(sourceSetName) { val sourceFile = this.kotlin.find { it.name == "module-info.java" } - val targetFile = compileKotlinTask.destinationDirectory.file("../module-info.class").get().asFile + val targetDirectory = compileKotlinTask.destinationDirectory.map { + it.dir("../${it.asFile.name}Module") + } // only configure the compilation if necessary if (sourceFile != null) { - // the default source set depends on this new source set - defaultSourceSet.dependsOn(this) + // register and wire a task to verify module-info.java content + // + // this will compile the whole sources again with a JPMS-aware target Java version, + // so that the Kotlin compiler can do the necessary verifications + // while compiling with `jdk-release=1.8` those verifications are not done + // + // this task is only going to be executed when running with `check` or explicitly, + // not during normal build operations + val verifyModuleTask = registerVerifyModuleTask( + compileKotlinTask, + sourceFile + ) + tasks.named("check") { + dependsOn(verifyModuleTask) + } // register a new compile module task - val compileModuleTask = registerCompileModuleTask(compileModuleTaskName, compileKotlinTask, sourceFile, targetFile) + val compileModuleTask = registerCompileModuleTask( + compileKotlinTask, + sourceFile, + targetDirectory + ) // add the resulting module descriptor to this target's artifact - artifactTask.dependsOn(compileModuleTask) - artifactTask.from(targetFile) { + artifactTask.from(compileModuleTask.map { it.destinationDirectory }) { if (multiRelease) { into("META-INF/versions/9/") } } } else { logger.info("No module-info.java file found in ${this.kotlin.srcDirs}, can't configure compilation of module-info!") - // remove the source set to prevent Gradle warnings - kotlin.sourceSets.remove(this) } + + // remove the source set to prevent Gradle warnings + kotlin.sourceSets.remove(this) } } } } - private fun Project.registerCompileModuleTask(taskName: String, compileTask: AbstractCompile, sourceFile: File, targetFile: File) = - tasks.register(taskName, JavaCompile::class) { - // Also add the module-info.java source file to the Kotlin compile task; - // the Kotlin compiler will parse and check module dependencies, - // but it currently won't compile to a module-info.class file. - compileTask.source(sourceFile) - - - // Configure the module compile task. - dependsOn(compileTask) + /** + * Add a Kotlin compile task that compiles `module-info.java` source file and Kotlin sources together, + * the Kotlin compiler will parse and check module dependencies, + * but it currently won't compile to a module-info.class file. + */ + private fun Project.registerVerifyModuleTask( + compileTask: KotlinCompile, + sourceFile: File + ): TaskProvider<out KotlinJvmCompile> { + apply<KotlinApiPlugin>() + val verifyModuleTaskName = "verify${compileTask.name.removePrefix("compile").capitalize()}Module" + // work-around for https://youtrack.jetbrains.com/issue/KT-60542 + val kotlinApiPlugin = plugins.getPlugin(KotlinApiPlugin::class) + val verifyModuleTask = kotlinApiPlugin.registerKotlinJvmCompileTask( + verifyModuleTaskName, + compileTask.compilerOptions.moduleName.get() + ) + verifyModuleTask { + group = VERIFICATION_GROUP + description = "Verify Kotlin sources for JPMS problems" + libraries.from(compileTask.libraries) + source(compileTask.sources) + source(compileTask.javaSources) + // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 + @Suppress("INVISIBLE_MEMBER") + source(compileTask.scriptSources) source(sourceFile) - outputs.file(targetFile) - classpath = files() - destinationDirectory.set(compileTask.destinationDirectory) - sourceCompatibility = JavaVersion.VERSION_1_9.toString() - targetCompatibility = JavaVersion.VERSION_1_9.toString() - - doFirst { - // Provide the module path to the compiler instead of using a classpath. - // The module path should be the same as the classpath of the compiler. - options.compilerArgs = listOf( - "--release", "9", - "--module-path", compileTask.classpath.asPath, - "-Xlint:-requires-transitive-automatic" + destinationDirectory.set(temporaryDir) + multiPlatformEnabled.set(compileTask.multiPlatformEnabled) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_9) + // To support LV override when set in aggregate builds + languageVersion.set(compileTask.compilerOptions.languageVersion) + freeCompilerArgs.addAll( + listOf("-Xjdk-release=9", "-Xsuppress-version-warnings", "-Xexpect-actual-classes") ) + optIn.addAll(compileTask.kotlinOptions.options.optIn) + } + // work-around for https://youtrack.jetbrains.com/issue/KT-60583 + inputs.files( + libraries.asFileTree.elements.map { libs -> + libs + .filter { it.asFile.exists() } + .map { + zipTree(it.asFile).filter { it.name == "module-info.class" } + } + } + ).withPropertyName("moduleInfosOfLibraries") + this as KotlinCompile + val kotlinPluginVersion = KotlinToolingVersion(kotlinApiPlugin.pluginVersion) + if (kotlinPluginVersion <= KotlinToolingVersion("1.9.255")) { + // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 + @Suppress("UNCHECKED_CAST") + val ownModuleNameProp = (this::class.superclasses.first() as KClass<AbstractKotlinCompile<*>>) + .declaredMemberProperties + .find { it.name == "ownModuleName" } + ?.get(this) as? Property<String> + ownModuleNameProp?.set(compileTask.kotlinOptions.moduleName) } - doLast { - // Move the compiled file out of the Kotlin compile task's destination dir, - // so it won't disturb Gradle's caching mechanisms. - val compiledFile = destinationDirectory.file(targetFile.name).get().asFile - targetFile.parentFile.mkdirs() - compiledFile.renameTo(targetFile) + val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT) + @OptIn(InternalKotlinGradlePluginApi::class) + if (taskKotlinLanguageVersion.get() < KotlinVersion.KOTLIN_2_0) { + // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 + @Suppress("INVISIBLE_MEMBER") + commonSourceSet.from(compileTask.commonSourceSet) + } else { + multiplatformStructure.refinesEdges.set(compileTask.multiplatformStructure.refinesEdges) + multiplatformStructure.fragments.set(compileTask.multiplatformStructure.fragments) } + // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 + // and work-around for https://youtrack.jetbrains.com/issue/KT-60582 + incremental = false } + return verifyModuleTask + } + + private fun Project.registerCompileModuleTask( + compileTask: KotlinCompile, + sourceFile: File, + targetDirectory: Provider<out Directory> + ) = tasks.register("${compileTask.name}Module", JavaCompile::class) { + // Configure the module compile task. + source(sourceFile) + classpath = files() + destinationDirectory.set(targetDirectory) + // use a Java 11 toolchain with release 9 option + // because for some OS / architecture combinations + // there are no Java 9 builds available + javaCompiler.set( + this@registerCompileModuleTask.the<JavaToolchainService>().compilerFor { + languageVersion.set(JavaLanguageVersion.of(11)) + } + ) + options.release.set(9) + + options.compilerArgumentProviders.add(object : CommandLineArgumentProvider { + @get:CompileClasspath + val compileClasspath = compileTask.libraries + + @get:CompileClasspath + val compiledClasses = compileTask.destinationDirectory + + @get:Input + val moduleName = sourceFile + .readLines() + .single { it.contains("module ") } + .substringAfter("module ") + .substringBefore(' ') + .trim() + + override fun asArguments() = mutableListOf( + // Provide the module path to the compiler instead of using a classpath. + // The module path should be the same as the classpath of the compiler. + "--module-path", + compileClasspath.asPath, + "--patch-module", + "$moduleName=${compiledClasses.get()}", + "-Xlint:-requires-transitive-automatic" + ) + }) + } } |