Skip to content
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

Add explicit module-info's for JPMS compatability #1624

Merged
merged 3 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ repositories {
mavenCentral()
}

dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30-RC")
}

kotlinDslPluginOptions {
experimentalWarning.set(false)
}
108 changes: 108 additions & 0 deletions buildSrc/src/main/kotlin/Java9Modularity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import org.gradle.api.*
import org.gradle.api.file.*
import org.gradle.api.tasks.bundling.*
import org.gradle.api.tasks.compile.*
import org.gradle.kotlin.dsl.*
import org.gradle.util.GUtil.*
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.targets.jvm.*
import java.io.*
import java.lang.module.*
import java.util.spi.*

object Java9Modularity {

@JvmStatic
@JvmOverloads
fun Project.configureJava9ModuleInfo(multiRelease: Boolean = true) {
val kotlin = extensions.findByType<KotlinProjectExtension>() ?: return
val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*> }
if (jvmTargets.isEmpty()) {
logger.warn("No Kotlin JVM targets found, can't configure compilation of module-info!")
}
jvmTargets.forEach { target ->
target.compilations.forEach { compilation ->
val defaultSourceSet = compilation.defaultSourceSet.kotlin
val moduleInfoSourceFile = defaultSourceSet.find { it.name == "module-info.java" }

if (moduleInfoSourceFile == null) {
logger.info("No module-info.java file found in ${defaultSourceSet.srcDirs}, can't configure compilation of module-info!")
} else {
val targetName = toCamelCase(target.targetName)
val compilationName = if (compilation.name != KotlinCompilation.MAIN_COMPILATION_NAME) toCamelCase(compilation.name) else ""
val compileModuleInfoTaskName = "compile${compilationName}ModuleInfo$targetName"
val checkModuleInfoTaskName = "check${compilationName}ModuleInfo$targetName"

val compileKotlinTask = compilation.compileKotlinTask as AbstractCompile
val modulePath = compileKotlinTask.classpath
val moduleInfoClassFile = compileKotlinTask.destinationDirectory.file("module-info.class").get().asFile

val compileModuleInfoTask = registerCompileModuleInfoTask(compileModuleInfoTaskName, modulePath, compileKotlinTask.destinationDirectory, moduleInfoSourceFile)
tasks.getByName(compilation.compileAllTaskName).dependsOn(compileModuleInfoTask)

val checkModuleInfoTask = registerCheckModuleInfoTask(checkModuleInfoTaskName, modulePath, moduleInfoClassFile)
checkModuleInfoTask.configure { dependsOn(compilation.compileAllTaskName) }
tasks.getByName("check").dependsOn(checkModuleInfoTask)
}
}

if (multiRelease) {
tasks.getByName<Jar>(target.artifactsTaskName) {
rename("module-info.class", "META-INF/versions/9/module-info.class")
manifest {
attributes("Multi-Release" to true)
}
}
}
}
}

private fun Project.registerCompileModuleInfoTask(taskName: String, modulePath: FileCollection, destinationDir: DirectoryProperty, moduleInfoSourceFile: File) =
tasks.register(taskName, JavaCompile::class) {
dependsOn(modulePath)
source(moduleInfoSourceFile)
classpath = files()
destinationDirectory.set(destinationDir)
sourceCompatibility = JavaVersion.VERSION_1_9.toString()
targetCompatibility = JavaVersion.VERSION_1_9.toString()
doFirst {
options.compilerArgs = listOf(
"--release", "9",
"--module-path", modulePath.asPath,
"-Xlint:-requires-transitive-automatic"
)
}
}

private fun Project.registerCheckModuleInfoTask(taskName: String, modulePath: FileCollection, moduleInfoClassFile: File) =
tasks.register(taskName) {
dependsOn(modulePath)
doLast {
val jdeps = ToolProvider.findFirst("jdeps").orElseThrow { IllegalStateException("Tool 'jdeps' is not available") }
val moduleDescriptor = moduleInfoClassFile.inputStream().use { ModuleDescriptor.read(it) }
val moduleName = moduleDescriptor.name()
val expectedOutput = moduleDescriptor.toJdepsOutput(moduleInfoClassFile)

val outputCaptureStream = ByteArrayOutputStream()
val printStream = PrintStream(outputCaptureStream, true, Charsets.UTF_8)
jdeps.run(
printStream, printStream,
"--multi-release", "9",
"--module-path", (modulePath + files(moduleInfoClassFile.parentFile)).asPath,
"--check", moduleName
)
val actualOutput = outputCaptureStream.toString(Charsets.UTF_8).trim()

if (actualOutput != expectedOutput) {
throw IllegalStateException("Module-info does not match!\n$actualOutput")
lion7 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

private fun ModuleDescriptor.toJdepsOutput(file: File, separator: String = System.lineSeparator()) =
"${name()} (${file.parentFile.toURI().toString().replace("file:", "file://")})$separator [Module descriptor]$separator" +
requires().sortedBy { it.name() }.joinToString(separator) { requirement -> " requires $requirement;" }
}
2 changes: 2 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) {
)
}
}

Java9Modularity.configureJava9ModuleInfo(project)
10 changes: 10 additions & 0 deletions core/jvmMain/src/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module kotlinx.serialization.core {
requires transitive kotlin.stdlib;

exports kotlinx.serialization;
exports kotlinx.serialization.builtins;
exports kotlinx.serialization.descriptors;
exports kotlinx.serialization.encoding;
exports kotlinx.serialization.internal;
exports kotlinx.serialization.modules;
}
5 changes: 5 additions & 0 deletions docs/building.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Building Kotlin Serialization from the source

## JDK version

To build Kotlin Serialization JDK version 9 or higher is required.
This is needed to compile the `module-info` file included for JPMS support.

## Runtime library

Kotlin Serialization runtime library itself is a [multiplatform](http://kotlinlang.org/docs/reference/multiplatform.html) project.
Expand Down
2 changes: 2 additions & 0 deletions formats/cbor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ kotlin {
}
}
}

Java9Modularity.configureJava9ModuleInfo(project)
6 changes: 6 additions & 0 deletions formats/cbor/jvmMain/src/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module kotlinx.serialization.cbor {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;

exports kotlinx.serialization.cbor;
}
4 changes: 3 additions & 1 deletion formats/hocon/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ dependencies {
compile project(':kotlinx-serialization-core')
api 'org.jetbrains.kotlin:kotlin-stdlib'

api 'com.typesafe:config:1.3.2'
api 'com.typesafe:config:1.4.1'

testCompile "org.jetbrains.kotlin:kotlin-test"
testCompile group: 'junit', name: 'junit', version: '4.12'
}

Java9Modularity.configureJava9ModuleInfo(project)
7 changes: 7 additions & 0 deletions formats/hocon/src/main/kotlin/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module kotlinx.serialization.hocon {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;
requires transitive typesafe.config;

exports kotlinx.serialization.hocon;
}
2 changes: 2 additions & 0 deletions formats/json/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ kotlin {
compileTestKotlinJsLegacy {
exclude '**/PropertyInitializerTest.kt'
}

Java9Modularity.configureJava9ModuleInfo(project)
6 changes: 6 additions & 0 deletions formats/json/jvmMain/src/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module kotlinx.serialization.json {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;

exports kotlinx.serialization.json;
}
2 changes: 2 additions & 0 deletions formats/properties/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ kotlin {
}
}
}

Java9Modularity.configureJava9ModuleInfo(project)
6 changes: 6 additions & 0 deletions formats/properties/jvmMain/src/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module kotlinx.serialization.properties {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;

exports kotlinx.serialization.properties;
}
2 changes: 2 additions & 0 deletions formats/protobuf/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ sourceSets.test.proto {
compileTestKotlinJvm {
dependsOn 'generateTestProto'
}

Java9Modularity.configureJava9ModuleInfo(project)
7 changes: 7 additions & 0 deletions formats/protobuf/jvmMain/src/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module kotlinx.serialization.protobuf {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;

exports kotlinx.serialization.protobuf;
exports kotlinx.serialization.protobuf.schema;
}