将 kotlin 多平台库发布到 Maven Central(InvalidMavenPublicationException multiple artifacts with the identical ...)

Publish kotlin multiplatform library to Maven Central (InvalidMavenPublicationException multiple artifacts with the identical ...)

由于 Jcenter 即将关闭,我正在尝试将我的库迁移到 Maven Central。我进行了很多搜索以找到任何有效的脚本,但没有运气。有 official docs,但它就像一个笑话,只是告诉将 maven-publish 插件添加到 gradle 脚本中,瞧,就是这样。

目前我遇到错误:

Caused by: org.gradle.api.publish.maven.InvalidMavenPublicationException: Invalid publication 'js': multiple artifacts with the identical extension and classifier ('jar', 'sources').

我的脚本如下所示:

plugins {
    id("kotlin-multiplatform")
    id("org.jetbrains.dokka") version "1.4.0-rc"
    `maven-publish`
    signing
}

kotlin {
    sourceSets {
        jvm()
        js() {
            nodejs()
            browser()
        }
        linuxX64()
        linuxArm64()
        mingwX64()
        macosX64()
        iosArm64()
        iosX64()

        val commonMain by getting {
            dependencies {
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }

        val jsMain by getting {
            dependencies {
            }
        }
        val jsTest by getting {
            dependencies {
                implementation(kotlin("test-js"))
            }
        }

        val jvmMain by getting {
            dependencies {
            }
        }
        val jvmTest by getting {
            dependencies {
                implementation(kotlin("test"))
                implementation(kotlin("test-junit"))
            }
        }

        val nativeMain by creating {
            dependsOn(commonMain)
            dependencies {
            }
        }

        val linuxX64Main by getting {
            dependsOn(nativeMain)
        }
        val linuxArm64Main by getting {
            dependsOn(nativeMain)
        }
        val mingwX64Main by getting {
            dependsOn(nativeMain)
        }
        val macosX64Main by getting {
            dependsOn(nativeMain)
        }
        val iosArm64Main by getting {
            dependsOn(nativeMain)
        }
        val iosX64Main by getting {
            dependsOn(nativeMain)
        }
    }
}

tasks {
    create<Jar>("javadocJar") {
        dependsOn(dokkaJavadoc)
        archiveClassifier.set("javadoc")
        from(dokkaJavadoc.get().outputDirectory)
    }

    dokkaJavadoc {
        println("Dokka !")
        dokkaSourceSets {
            create("commonMain") {
                displayName = "common"
                platform = "common"
            }
        }
    }
}

//  Publishing

val fis = FileInputStream("local.properties")
val properties = Properties().apply {
    load(fis)
}
val ossUser = properties.getProperty("oss.user")
val ossPassword = properties.getProperty("oss.password")
extra["signing.keyId"] = properties.getProperty("signing.keyId")
extra["signing.password"] = properties.getProperty("signing.password")
extra["signing.secretKeyRingFile"] = properties.getProperty("signing.secretKeyRingFile")

val libraryVersion: String by project
val publishedGroupId: String by project
val artifactName: String by project
val libraryName: String by project
val libraryDescription: String by project
val siteUrl: String by project
val gitUrl: String by project
val licenseName: String by project
val licenseUrl: String by project
val developerOrg: String by project
val developerName: String by project
val developerEmail: String by project
val developerId: String by project

project.group = publishedGroupId
project.version = libraryVersion

signing {
    sign(publishing.publications)
}

publishing {
    publications.withType(MavenPublication::class) {
        groupId = publishedGroupId
        artifactId = artifactName
        version = libraryVersion

        artifact(tasks["javadocJar"])
        artifact(tasks["sourcesJar"])

        pom {
            name.set(libraryName)
            description.set(libraryDescription)
            url.set(siteUrl)

            licenses {
                license {
                    name.set(licenseName)
                    url.set(licenseUrl)
                }
            }
            developers {
                developer {
                    id.set(developerId)
                    name.set(developerName)
                    email.set(developerEmail)
                }
            }
            organization {
                name.set(developerOrg)
            }
            scm {
                connection.set(gitUrl)
                developerConnection.set(gitUrl)
                url.set(siteUrl)
            }
        }
    }

    repositories {
        maven("https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
            name = "sonatype"
            credentials {
                username = ossUser
                password = ossPassword
            }
        }
    }
}

我也发现这个 reddit topic with no solution, this article 不起作用,还有很多其他的。关于如何发布到 bintray 的资料有很多,但现在已经无关紧要了

似乎问题出在这一行 artifact(tasks["sourcesJar"]),因为此任务已包括在内。
在这里,我想将我的工作脚本用于将 kotlin 多平台库上传到 Maven Central。
首先我们需要注册 Sonatype 帐户,验证我们的域等,这里是 fresh article with detailed steps.
那么您的项目脚本 build.gradle.kts 可能如下所示:

import java.io.FileInputStream
import java.util.Properties
import org.gradle.api.publish.PublishingExtension

plugins {
    id("kotlin-multiplatform")
    id("org.jetbrains.dokka") version "1.4.0-rc"
    id("io.codearte.nexus-staging") version "0.22.0"
    `maven-publish`
    signing
}

enum class OS {
    LINUX, WINDOWS, MAC
}

fun getHostOsName(): OS =
    System.getProperty("os.name").let { osName ->
        when {
            osName == "Linux" -> OS.LINUX
            osName.startsWith("Windows") -> OS.WINDOWS
            osName.startsWith("Mac") -> OS.MAC
            else -> throw GradleException("Unknown OS: $osName")
        }
    }

kotlin {
    sourceSets {
        jvm()
        js() {
            browser()
            nodejs()
        }
        when (getHostOsName()) {
            OS.LINUX -> {
                linuxX64()
                linuxArm64()
            }
            OS.WINDOWS -> {
                mingwX64()
            }
            OS.MAC -> {
                macosX64()
                iosArm64()
                iosX64()
            }
        }

        val commonMain by getting {
            dependencies {
                implementation(kotlin("stdlib-common"))
                implementation(Libs.olekdia.common)
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        val jvmMain by getting {
            dependencies {
            }
        }
        val jvmTest by getting {
            dependencies {
                implementation(kotlin("test"))
                implementation(kotlin("test-junit"))
            }
        }
        val jsMain by getting {
            dependencies {
            }
        }
        val nativeMain by creating {
            dependsOn(commonMain)
        }
        when (getHostOsName()) {
            OS.LINUX -> {
                val linuxX64Main by getting {
                    dependsOn(nativeMain)
                }
                val linuxArm64Main by getting {
                    dependsOn(nativeMain)
                }
            }
            OS.WINDOWS -> {
                val mingwX64Main by getting {
                    dependsOn(nativeMain)
                }
            }
            OS.MAC -> {
                val macosX64Main by getting {
                    dependsOn(nativeMain)
                }
                val iosArm64Main by getting {
                    dependsOn(nativeMain)
                }
                val iosX64Main by getting {
                    dependsOn(nativeMain)
                }
            }
        }
    }
}


tasks {
    create<Jar>("javadocJar") {
        dependsOn(dokkaJavadoc)
        archiveClassifier.set("javadoc")
        from(dokkaJavadoc.get().outputDirectory)
    }

    dokkaJavadoc {
        dokkaSourceSets {
            create("commonMain") {
                displayName = "common"
                platform = "common"
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------
//  Publishing
//--------------------------------------------------------------------------------------------------

val fis = FileInputStream("local.properties")
val properties = Properties().apply {
    load(fis)
}
val ossUser = properties.getProperty("oss.user")
val ossPassword = properties.getProperty("oss.password")
extra["signing.keyId"] = properties.getProperty("signing.keyId")
extra["signing.password"] = properties.getProperty("signing.password")
extra["signing.secretKeyRingFile"] = properties.getProperty("signing.secretKeyRingFile")

val libraryVersion: String by project
val publishedGroupId: String by project
val artifactName: String by project
val libraryName: String by project
val libraryDescription: String by project
val siteUrl: String by project
val gitUrl: String by project
val licenseName: String by project
val licenseUrl: String by project
val developerOrg: String by project
val developerName: String by project
val developerEmail: String by project
val developerId: String by project

project.group = publishedGroupId
project.version = libraryVersion

signing {
    sign(publishing.publications)
}

afterEvaluate {
    configure<PublishingExtension> {
        publications.all {
            val mavenPublication = this as? MavenPublication
            mavenPublication?.artifactId =
                "${project.name}${"-$name".takeUnless { "kotlinMultiplatform" in name }.orEmpty()}"
        }
    }
}

publishing {
    publications.withType(MavenPublication::class) {
        groupId = publishedGroupId
        artifactId = artifactName
        version = libraryVersion

        artifact(tasks["javadocJar"])

        pom {
            name.set(libraryName)
            description.set(libraryDescription)
            url.set(siteUrl)

            licenses {
                license {
                    name.set(licenseName)
                    url.set(licenseUrl)
                }
            }
            developers {
                developer {
                    id.set(developerId)
                    name.set(developerName)
                    email.set(developerEmail)
                }
            }
            organization {
                name.set(developerOrg)
            }
            scm {
                connection.set(gitUrl)
                developerConnection.set(gitUrl)
                url.set(siteUrl)
            }
        }
    }

    repositories {
        maven("https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
            name = "sonatype"
            credentials {
                username = ossUser
                password = ossPassword
            }
        }
    }
}

nexusStaging {
    username = ossUser
    password = ossPassword
    packageGroup = publishedGroupId
}

gradle.properties 中提供所需的库详细信息:

libraryVersion = 0.1.1
libraryName = Your library name
libraryDescription = Your library description
publishedGroupId = com.yourdomain
artifactName = your-cool-librayr
siteUrl = https://gitlab.com/yourlibrayr
gitUrl = https://gitlab.com/yourlibrayr.git
developerId = ...
developerOrg = ...
developerName = Your Name
developerEmail = yourmail@mail.com
licenseName = The Apache Software License, Version 2.0
licenseUrl = http://www.apache.org/licenses/LICENSE-2.0.txt
allLicenses = ["Apache-2.0"]
kotlin.mpp.enableGranularSourceSetsMetadata = true
gnsp.disableApplyOnlyOnRootProjectEnforcement = true

此处 gnsp.disableApplyOnlyOnRootProjectEnforcement = true 属性 需要声明 nexusStaging in subprojects.
最后把你的学分归功于 local.properties:

oss.user=your_user_name
oss.password=your_pass
signing.keyId=last_8_numbers_of_key
signing.password=your_pass
signing.secretKeyRingFile=/path/to/keystorage.gpg

现在在项目目录中发布打开的终端:

./gradlew build
./gradlew publish
./gradlew closeAndReleaseRepository
  • 您可以跳过最后一个命令,并关闭并释放 Nexus repository manager 中的暂存包。 nexus-staging 插件只需要从命令行执行。
  • 我曾尝试将脚本的发布部分移动到单独的文件中,并将其包含在 apply(from = "publish.gradle.kts") 中,但它没有用,因为它在单独的文件中丢失了上下文
  • 我使用旧版本的 dokka 库 (1.4.0-rc),因为新版本无法为所有平台生成 javadoc。存储库需要此 javadocs 进行发布。由于 authors mentioned 我们可以为此目的生成空的 javadoc.jar 文件。