-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add explicit module-info.java for JPMS compatibility #3297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright 2016-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.attributes.* | ||
import org.gradle.api.tasks.bundling.* | ||
import org.gradle.api.tasks.compile.* | ||
import org.gradle.jvm.toolchain.* | ||
import org.gradle.kotlin.dsl.* | ||
import org.jetbrains.kotlin.gradle.dsl.* | ||
|
||
/** | ||
* This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor. | ||
* The source file for the module descriptor is expected at <project-dir>/src/module-info.java. | ||
* | ||
* To maintain backwards compatibility with Java 8, the jvm JAR is marked as a multi-release JAR | ||
* with the module-info.class being moved to META-INF/versions/9/module-info.class. | ||
* | ||
* The Java toolchains feature of Gradle is used to detect or provision a JDK 11, | ||
* which is used to compile the module descriptor. | ||
*/ | ||
object Java9Modularity { | ||
|
||
@JvmStatic | ||
fun configure(project: Project) = with(project) { | ||
val javaToolchains = extensions.getByName("javaToolchains") as JavaToolchainService | ||
val target = when (val kotlin = extensions.getByName("kotlin")) { | ||
is KotlinJvmProjectExtension -> kotlin.target | ||
is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm") | ||
else -> throw IllegalStateException("Unknown Kotlin project extension in $project") | ||
} | ||
val compilation = target.compilations.getByName("main") | ||
|
||
// Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up. | ||
// For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772 | ||
configurations.getByName(compilation.compileDependencyConfigurationName).attributes { | ||
attribute( | ||
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, | ||
objects.named(LibraryElements::class, LibraryElements.JAR) | ||
) | ||
} | ||
|
||
val compileJavaModuleInfo = tasks.register("compileJavaModuleInfo", JavaCompile::class.java) { | ||
val moduleName = project.name.replace('-', '.') // this module's name | ||
val sourceFile = file("${target.name.ifEmpty { "." }}/src/module-info.java") | ||
if (!sourceFile.exists()) { | ||
throw IllegalStateException("$sourceFile not found in $project") | ||
} | ||
val compileKotlinTask = compilation.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
val compileKotlinTask = compilation.compileTaskProvider.get()
as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile
?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}") |
||
val targetDir = compileKotlinTask.destinationDirectory.dir("../java9") | ||
|
||
// Use a Java 11 compiler for the module-info. | ||
javaCompiler.set(javaToolchains.compilerFor { | ||
languageVersion.set(JavaLanguageVersion.of(11)) | ||
}) | ||
|
||
// Always compile kotlin classes before the module descriptor. | ||
dependsOn(compileKotlinTask) | ||
|
||
// Add the module-info source file. | ||
// Note that we use the parent dir and an include filter, | ||
// this is needed for Gradle's module detection to work in | ||
// org.gradle.api.tasks.compile.JavaCompile.createSpec | ||
source(sourceFile.parentFile) | ||
include { it.file == sourceFile } | ||
|
||
// The Kotlin compiler will parse and check module dependencies, | ||
// but it currently won't compile to a module-info.class file. | ||
// Note that module checking only works on JDK 9+, | ||
// because the JDK built-in base modules are not available in earlier versions. | ||
val javaVersion = compileKotlinTask.kotlinJavaToolchain.javaVersion.orNull | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to keep it lazy as well. You could do it by defining such class private class ModuleInfoFilter(
private val compileKotlinTaskPath: String,
private val javaVersionProvider: Provider<JavaVersion>,
private val moduleInfoFile: File,
private val logger: Logger
) : Spec<FileTreeElement> {
private val isJava9Compatible
get() = javaVersionProvider.orNull?.isJava9Compatible == true
private var logged = false
private fun logStatusOnce() {
if (logged) return
if (isJava9Compatible) {
logger.info("Module-info checking is enabled; $compileKotlinTaskPath is compiled using Java ${javaVersionProvider.get()}")
} else {
logger.info("Module-info checking is disabled")
}
logged = true
}
override fun isSatisfiedBy(element: FileTreeElement): Boolean {
logStatusOnce()
if (isJava9Compatible) return false
return element.file == moduleInfoFile
}
} and then use it like val javaVersionProvider = compileKotlinTask.kotlinJavaToolchain.javaVersion
compileKotlinTask.exclude(ModuleInfoFilter(compileKotlinTask.path, javaVersionProvider, sourceFile, logger)) Yes, it's more verbose, but performs the check lazily. Otherwise it depends on the order, so if the task configured before toolchains, then it may get an incorrect Java version. |
||
if (javaVersion?.isJava9Compatible == true) { | ||
logger.info("Module-info checking is enabled; $compileKotlinTask is compiled using Java $javaVersion") | ||
} else { | ||
logger.info("Module-info checking is disabled") | ||
// Exclude the module-info.java source file from the Kotlin compile task, | ||
// to prevent compile errors when resolving the module graph. | ||
compileKotlinTask.exclude { it.file == sourceFile } | ||
} | ||
|
||
// Set the task outputs and destination directory | ||
outputs.dir(targetDir) | ||
destinationDirectory.set(targetDir) | ||
|
||
// Configure JVM compatibility | ||
sourceCompatibility = JavaVersion.VERSION_1_9.toString() | ||
targetCompatibility = JavaVersion.VERSION_1_9.toString() | ||
|
||
// Set the Java release version. | ||
options.release.set(9) | ||
|
||
// Ignore warnings about using 'requires transitive' on automatic modules. | ||
// not needed when compiling with recent JDKs, e.g. 17 | ||
options.compilerArgs.add("-Xlint:-requires-transitive-automatic") | ||
|
||
// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly. | ||
val kotlinCompileDestinationDir = compileKotlinTask.destinationDirectory.asFile.get() | ||
options.compilerArgs.addAll(listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please try to preserve laziness here, e.g. like
|
||
|
||
// Use the classpath of the compileKotlinJvm task. | ||
// Also ensure that the module path is used instead of classpath. | ||
lion7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
classpath = compileKotlinTask.libraries | ||
modularity.inferModulePath.set(true) | ||
} | ||
|
||
tasks.getByName<Jar>(target.artifactsTaskName) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
manifest { | ||
attributes("Multi-Release" to true) | ||
} | ||
from(compileJavaModuleInfo) { | ||
into("META-INF/versions/9/") | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
// Currently the compilation of the module-info fails for | ||
// kotlinx-coroutines-play-services because it depends on Android JAR's | ||
// which do not have an explicit module-info descriptor. | ||
// Because the JAR's are all named `classes.jar`, | ||
// the automatic module name also becomes `classes`. | ||
// This conflicts since there are multiple JAR's with identical names. | ||
val invalidModules = listOf("kotlinx-coroutines-play-services") | ||
|
||
configure(subprojects.filter { | ||
!unpublished.contains(it.name) && !invalidModules.contains(it.name) && it.extensions.findByName("kotlin") != null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll be more correct to run the configuration once some Kotlin plugin is applied, like plugins.withType(org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin::class) {
// perform some configuration
} instead of checking for the extension presence right now. The order of plugin appliance should not matter. |
||
}) { | ||
Java9Modularity.configure(project) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module kotlinx.coroutines.guava { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires com.google.common; | ||
|
||
exports kotlinx.coroutines.guava; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@SuppressWarnings("JavaModuleNaming") | ||
module kotlinx.coroutines.jdk8 { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module kotlinx.coroutines.slf4j { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires org.slf4j; | ||
|
||
exports kotlinx.coroutines.slf4j; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||
import kotlinx.coroutines.CoroutineExceptionHandler; | ||||||
import kotlinx.coroutines.internal.MainDispatcherFactory; | ||||||
|
||||||
module kotlinx.coroutines.core { | ||||||
requires transitive kotlin.stdlib; | ||||||
requires kotlinx.atomicfu; | ||||||
|
||||||
// these are used by kotlinx.coroutines.debug.AgentPremain | ||||||
requires static java.instrument; // contains java.lang.instrument.* | ||||||
requires static jdk.unsupported; // contains sun.misc.Signal | ||||||
|
||||||
exports kotlinx.coroutines; | ||||||
exports kotlinx.coroutines.channels; | ||||||
exports kotlinx.coroutines.debug; | ||||||
exports kotlinx.coroutines.debug.internal; | ||||||
exports kotlinx.coroutines.flow; | ||||||
exports kotlinx.coroutines.flow.internal; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please elaborate on how exactly you got these packages? It seems like some of them should not be exported in the first place -- all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These packages are mostly used in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered exporting the internal packages just to the kotlinx.coroutines modules that depend on them? For example:
Suggested change
|
||||||
exports kotlinx.coroutines.future; | ||||||
exports kotlinx.coroutines.internal; | ||||||
exports kotlinx.coroutines.intrinsics; | ||||||
exports kotlinx.coroutines.scheduling; | ||||||
exports kotlinx.coroutines.selects; | ||||||
exports kotlinx.coroutines.stream; | ||||||
exports kotlinx.coroutines.sync; | ||||||
exports kotlinx.coroutines.time; | ||||||
|
||||||
uses CoroutineExceptionHandler; | ||||||
uses MainDispatcherFactory; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module kotlinx.coroutines.debug { | ||
requires java.management; | ||
requires java.instrument; | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires net.bytebuddy; | ||
requires net.bytebuddy.agent; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it going to work with our ByteBuddy shadowing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question, probably not. I will check this out further. |
||
requires org.junit.jupiter.api; | ||
requires org.junit.platform.commons; | ||
|
||
// exports kotlinx.coroutines.debug; // already exported by kotlinx.coroutines.core | ||
exports kotlinx.coroutines.debug.junit4; | ||
exports kotlinx.coroutines.debug.junit5; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import kotlinx.coroutines.internal.MainDispatcherFactory; | ||
import kotlinx.coroutines.test.internal.TestMainDispatcherFactory; | ||
|
||
module kotlinx.coroutines.test { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
|
||
exports kotlinx.coroutines.test; | ||
|
||
provides MainDispatcherFactory with TestMainDispatcherFactory; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
@SuppressWarnings("JavaModuleNaming") | ||
module kotlinx.coroutines.jdk9 { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires kotlinx.coroutines.reactive; | ||
requires org.reactivestreams; | ||
|
||
exports kotlinx.coroutines.jdk9; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module kotlinx.coroutines.reactive { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires kotlinx.atomicfu; | ||
lion7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
requires org.reactivestreams; | ||
|
||
exports kotlinx.coroutines.reactive; | ||
|
||
uses kotlinx.coroutines.reactive.ContextInjector; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import kotlinx.coroutines.reactive.ContextInjector; | ||
import kotlinx.coroutines.reactor.ReactorContextInjector; | ||
|
||
module kotlinx.coroutines.reactor { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires kotlinx.coroutines.reactive; | ||
requires org.reactivestreams; | ||
requires reactor.core; | ||
|
||
exports kotlinx.coroutines.reactor; | ||
|
||
provides ContextInjector with ReactorContextInjector; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@SuppressWarnings("JavaModuleNaming") | ||
module kotlinx.coroutines.rx2 { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires kotlinx.coroutines.reactive; | ||
requires kotlinx.atomicfu; | ||
requires io.reactivex.rxjava2; | ||
|
||
exports kotlinx.coroutines.rx2; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@SuppressWarnings("JavaModuleNaming") | ||
module kotlinx.coroutines.rx3 { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires kotlinx.coroutines.reactive; | ||
requires kotlinx.atomicfu; | ||
requires io.reactivex.rxjava3; | ||
|
||
exports kotlinx.coroutines.rx3; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import kotlinx.coroutines.android.AndroidDispatcherFactory; | ||
import kotlinx.coroutines.internal.MainDispatcherFactory; | ||
|
||
module kotlinx.coroutines.android { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
|
||
exports kotlinx.coroutines.android; | ||
|
||
provides MainDispatcherFactory with AndroidDispatcherFactory; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import kotlinx.coroutines.internal.MainDispatcherFactory; | ||
import kotlinx.coroutines.javafx.JavaFxDispatcherFactory; | ||
|
||
module kotlinx.coroutines.javafx { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires javafx.base; | ||
requires javafx.graphics; | ||
|
||
exports kotlinx.coroutines.javafx; | ||
|
||
provides MainDispatcherFactory with JavaFxDispatcherFactory; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import kotlinx.coroutines.internal.MainDispatcherFactory; | ||
import kotlinx.coroutines.swing.SwingDispatcherFactory; | ||
|
||
module kotlinx.coroutines.swing { | ||
requires kotlin.stdlib; | ||
requires kotlinx.coroutines.core; | ||
requires java.desktop; | ||
|
||
exports kotlinx.coroutines.swing; | ||
|
||
provides MainDispatcherFactory with SwingDispatcherFactory; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change it to