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