diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index cbd58e240..350ef426b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,6 +9,10 @@ repositories { mavenCentral() } +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30-RC") +} + kotlinDslPluginOptions { experimentalWarning.set(false) } diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt new file mode 100644 index 000000000..261fb9917 --- /dev/null +++ b/buildSrc/src/main/kotlin/Java9Modularity.kt @@ -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() ?: 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(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 requirements section does not match!\n$actualOutput") + } + } + } + + 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;" } +} diff --git a/core/build.gradle b/core/build.gradle index b30646163..c38da01b7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -42,3 +42,5 @@ tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) { ) } } + +Java9Modularity.configureJava9ModuleInfo(project) diff --git a/core/jvmMain/src/module-info.java b/core/jvmMain/src/module-info.java new file mode 100644 index 000000000..1d49cd808 --- /dev/null +++ b/core/jvmMain/src/module-info.java @@ -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; +} diff --git a/docs/building.md b/docs/building.md index 2702eff50..533cdcc8a 100644 --- a/docs/building.md +++ b/docs/building.md @@ -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. diff --git a/formats/cbor/build.gradle b/formats/cbor/build.gradle index 9868aae62..c62eb5968 100644 --- a/formats/cbor/build.gradle +++ b/formats/cbor/build.gradle @@ -29,3 +29,5 @@ kotlin { } } } + +Java9Modularity.configureJava9ModuleInfo(project) diff --git a/formats/cbor/jvmMain/src/module-info.java b/formats/cbor/jvmMain/src/module-info.java new file mode 100644 index 000000000..5ddd0ac39 --- /dev/null +++ b/formats/cbor/jvmMain/src/module-info.java @@ -0,0 +1,6 @@ +module kotlinx.serialization.cbor { + requires transitive kotlin.stdlib; + requires transitive kotlinx.serialization.core; + + exports kotlinx.serialization.cbor; +} diff --git a/formats/hocon/build.gradle b/formats/hocon/build.gradle index 3be9521db..8b3924a1d 100644 --- a/formats/hocon/build.gradle +++ b/formats/hocon/build.gradle @@ -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) diff --git a/formats/hocon/src/main/kotlin/module-info.java b/formats/hocon/src/main/kotlin/module-info.java new file mode 100644 index 000000000..b828065ca --- /dev/null +++ b/formats/hocon/src/main/kotlin/module-info.java @@ -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; +} diff --git a/formats/json/build.gradle b/formats/json/build.gradle index c28c0e8f0..824056d15 100644 --- a/formats/json/build.gradle +++ b/formats/json/build.gradle @@ -28,3 +28,5 @@ kotlin { compileTestKotlinJsLegacy { exclude '**/PropertyInitializerTest.kt' } + +Java9Modularity.configureJava9ModuleInfo(project) diff --git a/formats/json/jvmMain/src/module-info.java b/formats/json/jvmMain/src/module-info.java new file mode 100644 index 000000000..c19220f0e --- /dev/null +++ b/formats/json/jvmMain/src/module-info.java @@ -0,0 +1,6 @@ +module kotlinx.serialization.json { + requires transitive kotlin.stdlib; + requires transitive kotlinx.serialization.core; + + exports kotlinx.serialization.json; +} diff --git a/formats/properties/build.gradle b/formats/properties/build.gradle index f6a59036c..b79c3e3bf 100644 --- a/formats/properties/build.gradle +++ b/formats/properties/build.gradle @@ -29,3 +29,5 @@ kotlin { } } } + +Java9Modularity.configureJava9ModuleInfo(project) diff --git a/formats/properties/jvmMain/src/module-info.java b/formats/properties/jvmMain/src/module-info.java new file mode 100644 index 000000000..7be4c18d4 --- /dev/null +++ b/formats/properties/jvmMain/src/module-info.java @@ -0,0 +1,6 @@ +module kotlinx.serialization.properties { + requires transitive kotlin.stdlib; + requires transitive kotlinx.serialization.core; + + exports kotlinx.serialization.properties; +} diff --git a/formats/protobuf/build.gradle b/formats/protobuf/build.gradle index b840a6e62..ba1a1b4b5 100644 --- a/formats/protobuf/build.gradle +++ b/formats/protobuf/build.gradle @@ -46,3 +46,5 @@ sourceSets.test.proto { compileTestKotlinJvm { dependsOn 'generateTestProto' } + +Java9Modularity.configureJava9ModuleInfo(project) diff --git a/formats/protobuf/jvmMain/src/module-info.java b/formats/protobuf/jvmMain/src/module-info.java new file mode 100644 index 000000000..e0d1b32ad --- /dev/null +++ b/formats/protobuf/jvmMain/src/module-info.java @@ -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; +} diff --git a/gradle.properties b/gradle.properties index 3705efd94..3f2f8a0bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ junit_version=4.12 jackson_version=2.10.0.pr1 dokka_version=1.4.20-multimodule-dev-7 native.deploy= -validator_version=0.5.0 +validator_version=0.7.1 knit_version=0.2.2 coroutines_version=1.3.9