Skip to content

Commit ca247ee

Browse files
committed
Add explicit module-info.java for JPMS compatibility
1 parent 110ca3d commit ca247ee

File tree

19 files changed

+265
-19
lines changed

19 files changed

+265
-19
lines changed

build.gradle

+14
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,20 @@ allprojects {
300300
CacheRedirector.configure(project)
301301
}
302302

303+
// --------------- Java 9 modularity ---------------
304+
305+
// Currently the compilation of the module-info fails for
306+
// kotlinx.coroutines.debug because it export packages
307+
// that also exist in kotlinx.coroutines.core.
308+
// Note that only 1 module can export a package.
309+
def invalidModules = ["kotlinx-coroutines-debug"]
310+
311+
configure(subprojects.findAll {
312+
!unpublished.contains(it.name) && !invalidModules.contains(it.name) && it.extensions.findByName("kotlin") != null
313+
}) {
314+
Java9Modularity.configure(project)
315+
}
316+
303317
// --------------- Configure sub-projects that are published ---------------
304318

305319
def publishTasks = getTasksByName("publish", true) + getTasksByName("publishNpm", true)
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
object Java9Modularity {
14+
15+
@JvmStatic
16+
fun Project.configure() {
17+
val javaToolchains = extensions.getByName("javaToolchains") as JavaToolchainService
18+
val target = when (val kotlin = extensions.getByName("kotlin")) {
19+
is KotlinJvmProjectExtension -> kotlin.target
20+
is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm")
21+
else -> throw IllegalStateException("Unknown Kotlin project extension in $project")
22+
}
23+
val compilation = target.compilations.getByName("main")
24+
25+
// Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up.
26+
// For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772
27+
configurations.getByName(compilation.compileDependencyConfigurationName).attributes {
28+
attribute(
29+
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
30+
objects.named(LibraryElements::class, LibraryElements.JAR)
31+
)
32+
}
33+
34+
val compileJavaModuleInfo = tasks.register("compileJavaModuleInfo", JavaCompile::class.java) {
35+
val moduleName = project.name.replace('-', '.') // this module's name
36+
val sourceFile = file("${target.name.ifEmpty { "." }}/java9/module-info.java")
37+
if (!sourceFile.exists()) {
38+
throw IllegalStateException("$sourceFile not found in $project")
39+
}
40+
val compileKotlinTask = compilation.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile
41+
val targetDir = compileKotlinTask.destinationDirectory.dir("../java9")
42+
43+
// Use a Java 11 compiler for the module-info.
44+
javaCompiler.set(javaToolchains.compilerFor {
45+
languageVersion.set(JavaLanguageVersion.of(11))
46+
})
47+
48+
// Always compile kotlin classes before the module descriptor.
49+
dependsOn(compileKotlinTask)
50+
51+
// Add the module-info source file.
52+
// Note that we use the parent dir and an include filter,
53+
// this is needed for Gradle's module detection to work in
54+
// org.gradle.api.tasks.compile.JavaCompile.createSpec
55+
source(sourceFile.parentFile)
56+
include { it.file == sourceFile }
57+
58+
// The Kotlin compiler will parse and check module dependencies,
59+
// but it currently won't compile to a module-info.class file.
60+
// Note that module checking only works on JDK 9+,
61+
// because the JDK built-in base modules are not available in earlier versions.
62+
val javaVersion = compileKotlinTask.kotlinJavaToolchain.javaVersion.getOrNull()
63+
if (javaVersion?.isJava9Compatible == true) {
64+
logger.info("Module-info checking is enabled; $compileKotlinTask is compiled using Java $javaVersion")
65+
} else {
66+
logger.info("Module-info checking is disabled")
67+
// Exclude the module-info.java source file from the Kotlin compile task,
68+
// to prevent compile errors when resolving the module graph.
69+
compileKotlinTask.exclude { it.file == sourceFile }
70+
}
71+
72+
// Set the task outputs and destination directory
73+
outputs.dir(targetDir)
74+
destinationDirectory.set(targetDir)
75+
76+
// Configure JVM compatibility
77+
sourceCompatibility = JavaVersion.VERSION_1_9.toString()
78+
targetCompatibility = JavaVersion.VERSION_1_9.toString()
79+
80+
// Set the Java release version.
81+
options.release.set(9)
82+
83+
// Ignore warnings about using 'requires transitive' on automatic modules.
84+
// not needed when compiling with recent JDKs, e.g. 17
85+
options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
86+
87+
// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
88+
val kotlinCompileDestinationDir = compileKotlinTask.destinationDirectory.asFile.get()
89+
options.compilerArgs.addAll(listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir"))
90+
91+
// Use the classpath of the compileKotlinJvm task.
92+
// Also ensure that the module path is used instead of classpath.
93+
classpath = compileKotlinTask.classpath
94+
modularity.inferModulePath.set(true)
95+
}
96+
97+
tasks.getByName<Jar>(target.artifactsTaskName) {
98+
manifest {
99+
attributes("Multi-Release" to true)
100+
}
101+
from(compileJavaModuleInfo) {
102+
into("META-INF/versions/9/")
103+
}
104+
}
105+
}
106+
}

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

-14
This file was deleted.

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

+16-5
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
val binFile = clz.classLoader.getResourceAsStream("DebugProbesKt.bin")!!
2423
val binContent = binFile.readBytes()
2524
if (overwrite) {
@@ -32,8 +31,20 @@ class PrecompiledDebugProbesTest {
3231
assertTrue(
3332
array.contentEquals(binContent),
3433
"Compiled DebugProbesKt.class does not match the file shipped as a resource in kotlinx-coroutines-core. " +
35-
"Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true and " +
34+
"Typically it happens because of the Kotlin version update (-> binary metadata). " +
35+
"In that case, run the same test with -Poverwrite.probes=true and " +
3636
"ensure that classfile has major version equal to 50 (Java 6 compliance)")
3737
}
3838
}
39+
40+
private fun assertJava6Compliance(classBytes: ByteArray) {
41+
DataInputStream(classBytes.inputStream()).use {
42+
val magic: Int = it.readInt()
43+
if (magic != -0x35014542) throw IllegalArgumentException("Not a valid class!")
44+
val minor: Int = it.readUnsignedShort()
45+
val major: Int = it.readUnsignedShort()
46+
assertEquals(50, major)
47+
assertEquals(0, minor)
48+
}
49+
}
3950
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module kotlinx.coroutines.guava {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
5+
exports kotlinx.coroutines.guava;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.jdk8 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
6+
exports kotlinx.coroutines.future;
7+
exports kotlinx.coroutines.stream;
8+
exports kotlinx.coroutines.time;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module kotlinx.coroutines.play.services {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
5+
exports kotlinx.coroutines.tasks;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module kotlinx.coroutines.slf4j {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
5+
exports kotlinx.coroutines.slf4j;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module kotlinx.coroutines.core {
2+
requires transitive kotlin.stdlib;
3+
requires kotlinx.atomicfu;
4+
5+
// these are used by kotlinx.coroutines.debug.AgentPremain
6+
requires static java.instrument; // contains java.lang.instrument.*
7+
requires static jdk.unsupported; // contains sun.misc.Signal
8+
9+
exports kotlinx.coroutines;
10+
exports kotlinx.coroutines.channels;
11+
exports kotlinx.coroutines.debug;
12+
exports kotlinx.coroutines.debug.internal;
13+
exports kotlinx.coroutines.flow;
14+
exports kotlinx.coroutines.flow.internal;
15+
exports kotlinx.coroutines.internal;
16+
exports kotlinx.coroutines.intrinsics;
17+
exports kotlinx.coroutines.scheduling;
18+
exports kotlinx.coroutines.selects;
19+
exports kotlinx.coroutines.sync;
20+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module kotlinx.coroutines.test {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
5+
exports kotlinx.coroutines.test;
6+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module kotlinx.coroutines.reactive {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires org.reactivestreams;
5+
6+
exports kotlinx.coroutines.reactive;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module kotlinx.coroutines.reactor {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires kotlinx.coroutines.reactive;
5+
6+
exports kotlinx.coroutines.reactor;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.rx2 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
requires kotlinx.coroutines.reactive;
6+
requires io.reactivex.rxjava2;
7+
8+
exports kotlinx.coroutines.rx2;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@SuppressWarnings("JavaModuleNaming")
2+
module kotlinx.coroutines.rx3 {
3+
requires kotlin.stdlib;
4+
requires kotlinx.coroutines.core;
5+
requires kotlinx.coroutines.reactive;
6+
requires io.reactivex.rxjava3;
7+
8+
exports kotlinx.coroutines.rx3;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module kotlinx.coroutines.android {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
5+
exports kotlinx.coroutines.android;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module kotlinx.coroutines.javafx {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires javafx.base;
5+
requires javafx.graphics;
6+
7+
exports kotlinx.coroutines.javafx;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module kotlinx.coroutines.swing {
2+
requires kotlin.stdlib;
3+
requires kotlinx.coroutines.core;
4+
requires java.desktop;
5+
6+
exports kotlinx.coroutines.swing;
7+
}

0 commit comments

Comments
 (0)