1 /* <lambda>null2 * Copyright 2017-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.file.* 7 import org.gradle.api.provider.* 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.language.base.plugins.LifecycleBasePlugin.* 14 import org.gradle.process.* 15 import org.jetbrains.kotlin.gradle.* 16 import org.jetbrains.kotlin.gradle.dsl.* 17 import org.jetbrains.kotlin.gradle.plugin.* 18 import org.jetbrains.kotlin.gradle.plugin.mpp.* 19 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.* 20 import org.jetbrains.kotlin.gradle.targets.jvm.* 21 import org.jetbrains.kotlin.gradle.tasks.* 22 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 23 import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 24 import org.jetbrains.kotlin.tooling.core.* 25 import java.io.* 26 import kotlin.reflect.* 27 import kotlin.reflect.full.* 28 29 object Java9Modularity { 30 31 @JvmStatic 32 @JvmOverloads 33 fun Project.configureJava9ModuleInfo(multiRelease: Boolean = true) { 34 val disableJPMS = this.rootProject.extra.has("disableJPMS") 35 val ideaActive = System.getProperty("idea.active") == "true" 36 if (disableJPMS || ideaActive) return 37 val kotlin = extensions.findByType<KotlinProjectExtension>() ?: return 38 val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*, *> } 39 if (jvmTargets.isEmpty()) { 40 logger.warn("No Kotlin JVM targets found, can't configure compilation of module-info!") 41 } 42 jvmTargets.forEach { target -> 43 val artifactTask = tasks.getByName<Jar>(target.artifactsTaskName) { 44 if (multiRelease) { 45 manifest { 46 attributes("Multi-Release" to true) 47 } 48 } 49 } 50 51 target.compilations.forEach { compilation -> 52 val compileKotlinTask = compilation.compileKotlinTask as KotlinCompile 53 val defaultSourceSet = compilation.defaultSourceSet 54 55 // derive the names of the source set and compile module task 56 val sourceSetName = defaultSourceSet.name + "Module" 57 58 kotlin.sourceSets.create(sourceSetName) { 59 val sourceFile = this.kotlin.find { it.name == "module-info.java" } 60 val targetDirectory = compileKotlinTask.destinationDirectory.map { 61 it.dir("../${it.asFile.name}Module") 62 } 63 64 // only configure the compilation if necessary 65 if (sourceFile != null) { 66 // register and wire a task to verify module-info.java content 67 // 68 // this will compile the whole sources again with a JPMS-aware target Java version, 69 // so that the Kotlin compiler can do the necessary verifications 70 // while compiling with `jdk-release=1.8` those verifications are not done 71 // 72 // this task is only going to be executed when running with `check` or explicitly, 73 // not during normal build operations 74 val verifyModuleTask = registerVerifyModuleTask( 75 compileKotlinTask, 76 sourceFile 77 ) 78 tasks.named("check") { 79 dependsOn(verifyModuleTask) 80 } 81 82 // register a new compile module task 83 val compileModuleTask = registerCompileModuleTask( 84 compileKotlinTask, 85 sourceFile, 86 targetDirectory 87 ) 88 89 // add the resulting module descriptor to this target's artifact 90 artifactTask.from(compileModuleTask.map { it.destinationDirectory }) { 91 if (multiRelease) { 92 into("META-INF/versions/9/") 93 } 94 } 95 } else { 96 logger.info("No module-info.java file found in ${this.kotlin.srcDirs}, can't configure compilation of module-info!") 97 } 98 99 // remove the source set to prevent Gradle warnings 100 kotlin.sourceSets.remove(this) 101 } 102 } 103 } 104 } 105 106 /** 107 * Add a Kotlin compile task that compiles `module-info.java` source file and Kotlin sources together, 108 * the Kotlin compiler will parse and check module dependencies, 109 * but it currently won't compile to a module-info.class file. 110 */ 111 private fun Project.registerVerifyModuleTask( 112 compileTask: KotlinCompile, 113 sourceFile: File 114 ): TaskProvider<out KotlinJvmCompile> { 115 apply<KotlinApiPlugin>() 116 val verifyModuleTaskName = "verify${compileTask.name.removePrefix("compile").capitalize()}Module" 117 // work-around for https://youtrack.jetbrains.com/issue/KT-60542 118 val kotlinApiPlugin = plugins.getPlugin(KotlinApiPlugin::class) 119 val verifyModuleTask = kotlinApiPlugin.registerKotlinJvmCompileTask( 120 verifyModuleTaskName, 121 compileTask.compilerOptions.moduleName.get() 122 ) 123 verifyModuleTask { 124 group = VERIFICATION_GROUP 125 description = "Verify Kotlin sources for JPMS problems" 126 libraries.from(compileTask.libraries) 127 source(compileTask.sources) 128 source(compileTask.javaSources) 129 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 130 @Suppress("INVISIBLE_MEMBER") 131 source(compileTask.scriptSources) 132 source(sourceFile) 133 destinationDirectory.set(temporaryDir) 134 multiPlatformEnabled.set(compileTask.multiPlatformEnabled) 135 compilerOptions { 136 jvmTarget.set(JvmTarget.JVM_9) 137 // To support LV override when set in aggregate builds 138 languageVersion.set(compileTask.compilerOptions.languageVersion) 139 freeCompilerArgs.addAll( 140 listOf("-Xjdk-release=9", "-Xsuppress-version-warnings", "-Xexpect-actual-classes") 141 ) 142 optIn.addAll(compileTask.kotlinOptions.options.optIn) 143 } 144 // work-around for https://youtrack.jetbrains.com/issue/KT-60583 145 inputs.files( 146 libraries.asFileTree.elements.map { libs -> 147 libs 148 .filter { it.asFile.exists() } 149 .map { 150 zipTree(it.asFile).filter { it.name == "module-info.class" } 151 } 152 } 153 ).withPropertyName("moduleInfosOfLibraries") 154 this as KotlinCompile 155 val kotlinPluginVersion = KotlinToolingVersion(kotlinApiPlugin.pluginVersion) 156 if (kotlinPluginVersion <= KotlinToolingVersion("1.9.255")) { 157 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 158 @Suppress("UNCHECKED_CAST") 159 val ownModuleNameProp = (this::class.superclasses.first() as KClass<AbstractKotlinCompile<*>>) 160 .declaredMemberProperties 161 .find { it.name == "ownModuleName" } 162 ?.get(this) as? Property<String> 163 ownModuleNameProp?.set(compileTask.kotlinOptions.moduleName) 164 } 165 166 val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT) 167 @OptIn(InternalKotlinGradlePluginApi::class) 168 if (taskKotlinLanguageVersion.get() < KotlinVersion.KOTLIN_2_0) { 169 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 170 @Suppress("INVISIBLE_MEMBER") 171 commonSourceSet.from(compileTask.commonSourceSet) 172 } else { 173 multiplatformStructure.refinesEdges.set(compileTask.multiplatformStructure.refinesEdges) 174 multiplatformStructure.fragments.set(compileTask.multiplatformStructure.fragments) 175 } 176 // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 177 // and work-around for https://youtrack.jetbrains.com/issue/KT-60582 178 incremental = false 179 } 180 return verifyModuleTask 181 } 182 183 private fun Project.registerCompileModuleTask( 184 compileTask: KotlinCompile, 185 sourceFile: File, 186 targetDirectory: Provider<out Directory> 187 ) = tasks.register("${compileTask.name}Module", JavaCompile::class) { 188 // Configure the module compile task. 189 source(sourceFile) 190 classpath = files() 191 destinationDirectory.set(targetDirectory) 192 // use a Java 11 toolchain with release 9 option 193 // because for some OS / architecture combinations 194 // there are no Java 9 builds available 195 javaCompiler.set( 196 this@registerCompileModuleTask.the<JavaToolchainService>().compilerFor { 197 languageVersion.set(JavaLanguageVersion.of(11)) 198 } 199 ) 200 options.release.set(9) 201 202 options.compilerArgumentProviders.add(object : CommandLineArgumentProvider { 203 @get:CompileClasspath 204 val compileClasspath = compileTask.libraries 205 206 @get:CompileClasspath 207 val compiledClasses = compileTask.destinationDirectory 208 209 @get:Input 210 val moduleName = sourceFile 211 .readLines() 212 .single { it.contains("module ") } 213 .substringAfter("module ") 214 .substringBefore(' ') 215 .trim() 216 217 override fun asArguments() = mutableListOf( 218 // Provide the module path to the compiler instead of using a classpath. 219 // The module path should be the same as the classpath of the compiler. 220 "--module-path", 221 compileClasspath.asPath, 222 "--patch-module", 223 "$moduleName=${compiledClasses.get()}", 224 "-Xlint:-requires-transitive-automatic" 225 ) 226 }) 227 } 228 } 229