将 kotlin 多平台库发布到 Maven CentralInvalidMavenPublicationException multiple artifacts with the same ...

问题描述

由于 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 没有解决方案,此 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 in subprojectsnexusStaging 属性。
最后把你的功劳记在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。存储库需要此 javadoc 才能发布。作为 authors mentioned,我们可以为此生成空的 javadoc.jar 文件。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...