import groovy.util.* import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.* import org.gradle.plugins.signing.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.tasks.* import java.net.* /* * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ // Configures publishing of Maven artifacts to MavenCentral plugins { `maven-publish` signing } val isMultiplatform = name in listOf( "kotlinx-serialization-core", "kotlinx-serialization-json", "kotlinx-serialization-json-okio", "kotlinx-serialization-json-io", "kotlinx-serialization-json-tests", "kotlinx-serialization-protobuf", "kotlinx-serialization-cbor", "kotlinx-serialization-properties" ) val isBom = name == "kotlinx-serialization-bom" if (!isBom) { tasks.register("stubJavadoc") { archiveClassifier = "javadoc" } } tasks.register("emptyJar") afterEvaluate { val mainSourcesJar = tasks.register("mainSourcesJar") { archiveClassifier = "sources" if (isMultiplatform) { from(kotlinExtension.sourceSets.getByName("commonMain").kotlin) } else if (isBom) { // no-op: sourceSets is [] for BOM, as it does not have sources. } else { from(sourceSets.named("main").get().allSource) } } publishing { if (!isMultiplatform && !isBom) { publications.register("maven") { artifactId = project.name from(components["java"]) artifact(mainSourcesJar) artifact(tasks.named("stubJavadoc")) } } else { // Rename artifacts for backward compatibility publications.withType().configureEach { val type = name logger.info("Configuring $type") when (type) { "kotlinMultiplatform" -> { // With Kotlin 1.4.0, the root module ID has no suffix, but for compatibility with // the consumers who can't read Gradle module metadata, we publish the JVM artifacts in it artifactId = project.name reconfigureMultiplatformPublication(publications.getByName("jvm") as MavenPublication) } "metadata", "jvm", "js", "native" -> artifactId = "${project.name}-$type" } logger.info("Artifact id = $artifactId") // The 'root' module publishes the JVM module's Javadoc JAR as per reconfigureMultiplatformPublication, and // every other module should publish an empty Javadoc JAR. TODO: provide proper documentation artifacts? if (name != "kotlinMultiplatform" && !isBom) { artifact(tasks.named("stubJavadoc")) } } } publications.withType().configureEach { pom.configureMavenCentralMetadata() signPublicationIfKeyPresent() } } } val testRepositoryDir = project.layout.buildDirectory.dir("testRepository") publishing { repositories { addSonatypeRepository() /** * Maven repository in build directory to check published artifacts. */ maven { setUrl(testRepositoryDir) name = "test" } } } interface LocalArtifactAttr : Named { companion object { val ATTRIBUTE = Attribute.of( "kotlinx.kover.gradle-plugin", LocalArtifactAttr::class.java ) } } val testPublicationTask: TaskCollection<*> = tasks.named { name -> name == "publishAllPublicationsToTestRepository" } configurations.register("testPublication") { isVisible = false isCanBeResolved = false // this configuration produces modules that can be consumed by other projects isCanBeConsumed = true attributes { attribute(Attribute.of("kotlinx.serialization.repository", String::class.java), "test") } outgoing.artifact(testRepositoryDir) { builtBy(testPublicationTask) } } tasks.withType().configureEach { dependsOn(tasks.withType()) } // NOTE: This is a temporary WA, see KT-61313. // Task ':compileTestKotlin' uses this output of task ':signPublication' without declaring an explicit or implicit dependency tasks.withType().matching { it.name.startsWith("compileTestKotlin") }.configureEach { val targetName = name.substringAfter("compileTestKotlin") mustRunAfter(tasks.withType().named { it == "sign${targetName}Publication" }) } // NOTE: This is a temporary WA, see KT-61313. // Task ':linkDebugTest' uses this output of task ':signPublication' without declaring an explicit or implicit dependency tasks.withType() { val targetName = name.substringAfter("linkDebugTest") mustRunAfter(tasks.withType().named { it == "sign${targetName}Publication" }) } fun MavenPom.configureMavenCentralMetadata() { name = project.name description = "Kotlin multiplatform serialization runtime library" url = "https://github.com/Kotlin/kotlinx.serialization" licenses { license { name = "The Apache Software License, Version 2.0" url = "https://www.apache.org/licenses/LICENSE-2.0.txt" distribution = "repo" } } developers { developer { id = "JetBrains" name = "JetBrains Team" organization = "JetBrains" organizationUrl = "https://www.jetbrains.com" } } scm { url = "https://github.com/Kotlin/kotlinx.serialization" } } // utility functions /** * Re-configure common publication to depend on JVM artifact only in pom.xml. * * Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module * metadata can still get the platform artifact and transitive dependencies from the POM. * * Taken from https://github.com/Kotlin/kotlinx.coroutines */ public fun Project.reconfigureMultiplatformPublication(jvmPublication: MavenPublication) { val mavenPublications = extensions.getByType().publications.withType() val kmpPublication = mavenPublications.getByName("kotlinMultiplatform") var jvmPublicationXml: XmlProvider? = null jvmPublication.pom.withXml { jvmPublicationXml = this } kmpPublication.pom.withXml { val root = asNode() // Remove the original content and add the content from the platform POM: root.children().toList().forEach { root.remove(it as Node) } jvmPublicationXml!!.asNode().children().forEach { root.append(it as Node) } // Adjust the self artifact ID, as it should match the root module's coordinates: ((root["artifactId"] as NodeList).first() as Node).setValue(kmpPublication.artifactId) // Set packaging to POM to indicate that there's no artifact: root.appendNode("packaging", "pom") // Remove the original platform dependencies and add a single dependency on the platform module: val dependencies = (root["dependencies"] as NodeList).first() as Node dependencies.children().toList().forEach { dependencies.remove(it as Node) } dependencies.appendNode("dependency").apply { appendNode("groupId", jvmPublication.groupId) appendNode("artifactId", jvmPublication.artifactId) appendNode("version", jvmPublication.version) appendNode("scope", "compile") } } // TODO verify if this is still relevant tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication" }.configureEach { @Suppress("DEPRECATION") dependsOn("generatePomFileFor${jvmPublication.name.capitalize()}Publication") } } fun MavenPublication.signPublicationIfKeyPresent() { val keyId = getSensitiveProperty("libs.sign.key.id") val signingKey = getSensitiveProperty("libs.sign.key.private") val signingKeyPassphrase = getSensitiveProperty("libs.sign.passphrase") if (!signingKey.isNullOrBlank()) { extensions.configure("signing") { useInMemoryPgpKeys(keyId, signingKey, signingKeyPassphrase) sign(this@signPublicationIfKeyPresent) } } } fun RepositoryHandler.addSonatypeRepository() { maven { url = mavenRepositoryUri() credentials { username = getSensitiveProperty("libs.sonatype.user") password = getSensitiveProperty("libs.sonatype.password") } } } fun mavenRepositoryUri(): URI { // TODO -SNAPSHOT detection can be made here as well val repositoryId: String? = System.getenv("libs.repository.id") return if (repositoryId == null) { URI("https://oss.sonatype.org/service/local/staging/deploy/maven2/") } else { URI("https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId") } } fun getSensitiveProperty(name: String): String? { return findProperty(name) as? String ?: System.getenv(name) }