Skip to content

Commit bf8e323

Browse files
committed
Add explicit module-info.java for JPMS compatibility
1 parent 67e21b2 commit bf8e323

File tree

20 files changed

+320
-6
lines changed

20 files changed

+320
-6
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core
169169
}
170170

171171
apply plugin: "bom-conventions"
172+
apply plugin: "java-modularity-conventions"
172173

173174
if (build_snapshot_train) {
174175
println "Hacking test tasks, removing stress and flaky tests"
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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.tasks.bundling.*
8+
import org.gradle.api.tasks.compile.*
9+
import org.gradle.jvm.toolchain.*
10+
import org.gradle.kotlin.dsl.*
11+
import org.jetbrains.kotlin.gradle.dsl.*
12+
13+
/**
14+
* This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor.
15+
* The source file for the module descriptor is expected at <project-dir>/src/module-info.java.
16+
*
17+
* To maintain backwards compatibility with Java 8, the jvm JAR is marked as a multi-release JAR
18+
* with the module-info.class being moved to META-INF/versions/9/module-info.class.
19+
*
20+
* The Java toolchains feature of Gradle is used to detect or provision a JDK 11,
21+
* which is used to compile the module descriptor.
22+
*/
23+
object Java9Modularity {
24+
25+
@JvmStatic
26+
fun configure(project: Project) = with(project) {
27+
val javaToolchains = extensions.getByName("javaToolchains") as JavaToolchainService
28+
val target = when (val kotlin = extensions.getByName("kotlin")) {
29+
is KotlinJvmProjectExtension -> kotlin.target
30+
is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm")
31+
else -> throw IllegalStateException("Unknown Kotlin project extension in $project")
32+
}
33+
val compilation = target.compilations.getByName("main")
34+
35+
// Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up.
36+
// For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772
37+
configurations.getByName(compilation.compileDependencyConfigurationName).attributes {
38+
attribute(
39+
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
40+
objects.named(LibraryElements::class, LibraryElements.JAR)
41+
)
42+
}
43+
44+
val compileJavaModuleInfo = tasks.register("compileJavaModuleInfo", JavaCompile::class.java) {
45+
val moduleName = project.name.replace('-', '.') // this module's name
46+
val sourceFile = file("${target.name.ifEmpty { "." }}/src/module-info.java")
47+
if (!sourceFile.exists()) {
48+
throw IllegalStateException("$sourceFile not found in $project")
49+
}
50+
val compileKotlinTask = compilation.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile
51+
val targetDir = compileKotlinTask.destinationDirectory.dir("../java9")
52+
53+
// Use a Java 11 compiler for the module-info.
54+
javaCompiler.set(javaToolchains.compilerFor {
55+
languageVersion.set(JavaLanguageVersion.of(11))
56+
})
57+
58+
// Always compile kotlin classes before the module descriptor.
59+
dependsOn(compileKotlinTask)
60+
61+
// Add the module-info source file.
62+
// Note that we use the parent dir and an include filter,
63+
// this is needed for Gradle's module detection to work in
64+
// org.gradle.api.tasks.compile.JavaCompile.createSpec
65+
source(sourceFile.parentFile)
66+
include { it.file == sourceFile }
67+
68+
// The Kotlin compiler will parse and check module dependencies,
69+
// but it currently won't compile to a module-info.class file.
70+
// Note that module checking only works on JDK 9+,
71+
// because the JDK built-in base modules are not available in earlier versions.
72+
val javaVersion = compileKotlinTask.kotlinJavaToolchain.javaVersion.orNull
73+
if (javaVersion?.isJava9Compatible == true) {
74+
logger.info("Module-info checking is enabled; $compileKotlinTask is compiled using Java $javaVersion")
75+
} else {
76+
logger.info("Module-info checking is disabled")
77+
// Exclude the module-info.java source file from the Kotlin compile task,
78+
// to prevent compile errors when resolving the module graph.
79+
compileKotlinTask.exclude { it.file == sourceFile }
80+
}
81+
82+
// Set the task outputs and destination directory
83+
outputs.dir(targetDir)
84+
destinationDirectory.set(targetDir)
85+
86+
// Configure JVM compatibility
87+
sourceCompatibility = JavaVersion.VERSION_1_9.toString()
88+
targetCompatibility = JavaVersion.VERSION_1_9.toString()
89+
90+
// Set the Java release version.
91+
options.release.set(9)
92+
93+
// Ignore warnings about using 'requires transitive' on automatic modules.
94+
// not needed when compiling with recent JDKs, e.g. 17
95+
options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
96+
97+
// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
98+
val kotlinCompileDestinationDir = compileKotlinTask.destinationDirectory.asFile.get()
99+
options.compilerArgs.addAll(listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir"))
100+
101+
// Use the classpath of the compileKotlinJvm task.
102+
// Also ensure that the module path is used instead of classpath.
103+
classpath = compileKotlinTask.libraries
104+
modularity.inferModulePath.set(true)
105+
}
106+
107+
tasks.getByName<Jar>(target.artifactsTaskName) {
108+
manifest {
109+
attributes("Multi-Release" to true)
110+
}
111+
from(compileJavaModuleInfo) {
112+
into("META-INF/versions/9/")
113+
}
114+
}
115+
}
116+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// Currently the compilation of the module-info fails for
6+
// kotlinx-coroutines-play-services because it depends on Android JAR's
7+
// which do not have an explicit module-info descriptor.
8+
// Because the JAR's are all named `classes.jar`,
9+
// the automatic module name also becomes `classes`.
10+
// This conflicts since there are multiple JAR's with identical names.
11+
val invalidModules = listOf("kotlinx-coroutines-play-services")
12+
13+
configure(subprojects.filter {
14+
!unpublished.contains(it.name) && !invalidModules.contains(it.name) && it.extensions.findByName("kotlin") != null
15+
}) {
16+
Java9Modularity.configure(project)
17+
}

gradle/compile-jvm-multiplatform.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ kotlin {
1010
sourceSets {
1111
jvmMain.dependencies {
1212
compileOnly "org.codehaus.mojo:animal-sniffer-annotations:1.20"
13+
// Workaround until https://github.com/JetBrains/kotlin/pull/4999 is picked up
14+
api "org.jetbrains:annotations:23.0.0"
1315
}
1416

1517
jvmTest.dependencies {

integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ class PrecompiledDebugProbesTest {
1616
@Test
1717
fun testClassFileContent() {
1818
val clz = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt")
19-
val className: String = clz.getName()
20-
val classFileResourcePath = className.replace(".", "/") + ".class"
21-
val stream = clz.classLoader.getResourceAsStream(classFileResourcePath)!!
22-
val array = stream.readBytes()
19+
val classFileResourcePath = clz.name.replace(".", "/") + ".class"
20+
val array = clz.classLoader.getResourceAsStream(classFileResourcePath).use { it.readBytes() }
21+
assertJava6Compliance(array)
2322
// we expect the integration testing project to be in a subdirectory of the main kotlinx.coroutines project
2423
val base = File("").absoluteFile.parentFile
2524
val probes = File(base, "kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin")
@@ -31,8 +30,20 @@ class PrecompiledDebugProbesTest {
3130
assertTrue(
3231
array.contentEquals(binContent),
3332
"Compiled DebugProbesKt.class does not match the file shipped as a resource in kotlinx-coroutines-core. " +
34-
"Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true."
33+
"Typically it happens because of the Kotlin version update (-> binary metadata). " +
34+
"In that case, run the same test with -Poverwrite.probes=true."
3535
)
3636
}
3737
}
38+
39+
private fun assertJava6Compliance(classBytes: ByteArray) {
40+
DataInputStream(classBytes.inputStream()).use {
41+
val magic: Int = it.readInt()
42+
if (magic != -0x35014542) throw IllegalArgumentException("Not a valid class!")
43+
val minor: Int = it.readUnsignedShort()
44+
val major: Int = it.readUnsignedShort()
45+
assertEquals(50, major)
46+
assertEquals(0, minor)
47+
}
48+
}
3849
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module kotlinx.coroutines.guava {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires com.google.common;
5+
6+
exports kotlinx.coroutines.guava;
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.jdk8 {
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module kotlinx.coroutines.slf4j {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires org.slf4j;
5+
6+
exports kotlinx.coroutines.slf4j;
7+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import kotlinx.coroutines.CoroutineExceptionHandler;
2+
import kotlinx.coroutines.internal.MainDispatcherFactory;
3+
4+
module kotlinx.coroutines.core {
5+
requires transitive kotlin.stdlib;
6+
requires kotlinx.atomicfu;
7+
8+
// these are used by kotlinx.coroutines.debug.AgentPremain
9+
requires static java.instrument; // contains java.lang.instrument.*
10+
requires static jdk.unsupported; // contains sun.misc.Signal
11+
12+
exports kotlinx.coroutines;
13+
exports kotlinx.coroutines.channels;
14+
exports kotlinx.coroutines.debug;
15+
exports kotlinx.coroutines.debug.internal;
16+
exports kotlinx.coroutines.flow;
17+
exports kotlinx.coroutines.flow.internal;
18+
exports kotlinx.coroutines.future;
19+
exports kotlinx.coroutines.internal;
20+
exports kotlinx.coroutines.intrinsics;
21+
exports kotlinx.coroutines.scheduling;
22+
exports kotlinx.coroutines.selects;
23+
exports kotlinx.coroutines.stream;
24+
exports kotlinx.coroutines.sync;
25+
exports kotlinx.coroutines.time;
26+
27+
uses CoroutineExceptionHandler;
28+
uses MainDispatcherFactory;
29+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module kotlinx.coroutines.debug {
2+
requires java.management;
3+
requires java.instrument;
4+
requires kotlin.stdlib;
5+
requires kotlinx.coroutines.core;
6+
requires net.bytebuddy;
7+
requires net.bytebuddy.agent;
8+
requires org.junit.jupiter.api;
9+
requires org.junit.platform.commons;
10+
11+
// exports kotlinx.coroutines.debug; // already exported by kotlinx.coroutines.core
12+
exports kotlinx.coroutines.debug.junit4;
13+
exports kotlinx.coroutines.debug.junit5;
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import kotlinx.coroutines.internal.MainDispatcherFactory;
2+
import kotlinx.coroutines.test.internal.TestMainDispatcherFactory;
3+
4+
module kotlinx.coroutines.test {
5+
requires kotlin.stdlib;
6+
requires kotlinx.coroutines.core;
7+
8+
exports kotlinx.coroutines.test;
9+
10+
provides MainDispatcherFactory with TestMainDispatcherFactory;
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.jdk9 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
requires kotlinx.coroutines.reactive;
6+
requires org.reactivestreams;
7+
8+
exports kotlinx.coroutines.jdk9;
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module kotlinx.coroutines.reactive {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires kotlinx.atomicfu;
5+
requires org.reactivestreams;
6+
7+
exports kotlinx.coroutines.reactive;
8+
9+
uses kotlinx.coroutines.reactive.ContextInjector;
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import kotlinx.coroutines.reactive.ContextInjector;
2+
import kotlinx.coroutines.reactor.ReactorContextInjector;
3+
4+
module kotlinx.coroutines.reactor {
5+
requires kotlin.stdlib;
6+
requires kotlinx.coroutines.core;
7+
requires kotlinx.coroutines.reactive;
8+
requires org.reactivestreams;
9+
requires reactor.core;
10+
11+
exports kotlinx.coroutines.reactor;
12+
13+
provides ContextInjector with ReactorContextInjector;
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.rx2 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
requires kotlinx.coroutines.reactive;
6+
requires kotlinx.atomicfu;
7+
requires io.reactivex.rxjava2;
8+
9+
exports kotlinx.coroutines.rx2;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.rx3 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
requires kotlinx.coroutines.reactive;
6+
requires kotlinx.atomicfu;
7+
requires io.reactivex.rxjava3;
8+
9+
exports kotlinx.coroutines.rx3;
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import kotlinx.coroutines.android.AndroidDispatcherFactory;
2+
import kotlinx.coroutines.internal.MainDispatcherFactory;
3+
4+
module kotlinx.coroutines.android {
5+
requires kotlin.stdlib;
6+
requires kotlinx.coroutines.core;
7+
8+
exports kotlinx.coroutines.android;
9+
10+
provides MainDispatcherFactory with AndroidDispatcherFactory;
11+
}

ui/kotlinx-coroutines-javafx/build.gradle.kts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
buildscript {
6+
dependencies {
7+
// this line can be removed when https://github.com/openjfx/javafx-gradle-plugin/pull/135 is released
8+
classpath("org.javamodularity:moduleplugin:1.8.12")
9+
}
10+
}
11+
512
plugins {
6-
id("org.openjfx.javafxplugin") version "0.0.9"
13+
id("org.openjfx.javafxplugin") version "0.0.13"
714
}
815

916
configurations {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import kotlinx.coroutines.internal.MainDispatcherFactory;
2+
import kotlinx.coroutines.javafx.JavaFxDispatcherFactory;
3+
4+
module kotlinx.coroutines.javafx {
5+
requires kotlin.stdlib;
6+
requires kotlinx.coroutines.core;
7+
requires javafx.base;
8+
requires javafx.graphics;
9+
10+
exports kotlinx.coroutines.javafx;
11+
12+
provides MainDispatcherFactory with JavaFxDispatcherFactory;
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import kotlinx.coroutines.internal.MainDispatcherFactory;
2+
import kotlinx.coroutines.swing.SwingDispatcherFactory;
3+
4+
module kotlinx.coroutines.swing {
5+
requires kotlin.stdlib;
6+
requires kotlinx.coroutines.core;
7+
requires java.desktop;
8+
9+
exports kotlinx.coroutines.swing;
10+
11+
provides MainDispatcherFactory with SwingDispatcherFactory;
12+
}

0 commit comments

Comments
 (0)