15、发布与签名:Maven Central发布、Artifactory、GPG签名

说实话,发布这件事,在Gradle生态里一直是个「看起来简单,做起来坑多」的活儿。我最早做开源库的时候,以为配个uploadArchives就完事了,结果被GPG签名折腾了一整天。嗯,今天咱们就把这块彻底讲透。

15.1 发布的核心流程

发布一个库到Maven Central,说白了就是三步:打包 → 签名 → 上传。但每一步都有讲究。

核心流程概览:

  • 生成JAR、Sources JAR、Javadoc JAR
  • 用GPG对产物进行签名(生成.asc文件)
  • 上传到Maven Central的Staging仓库
  • 在Sonatype的Nexus UI上执行Close和Release操作

我个人习惯用maven-publish插件配合signing插件来做。为什么不用旧的uploadArchives?因为maven-publish是官方推荐的,而且对Kotlin DSL支持更好。

1. 配置Gradle插件 2. 配置GPG签名 3. 执行publish任务 4. Sonatype Nexus: Close & Release ✅ 发布完成

15.2 配置maven-publish插件

先看一个最基础的配置。我在项目中通常这样写:

// build.gradle.kts
plugins {
    `maven-publish`
    signing
}

publishing {
    publications {
        register<MavenPublication>("release") {
            from(components["java"])
            
            // 这些信息会出现在POM文件中
            pom {
                name.set("My Library")
                description.set("A useful library for Android")
                url.set("https://github.com/your/repo")
                
                licenses {
                    license {
                        name.set("The Apache License, Version 2.0")
                        url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
                    }
                }
                
                developers {
                    developer {
                        id.set("your-id")
                        name.set("Your Name")
                        email.set("your@email.com")
                    }
                }
                
                scm {
                    connection.set("scm:git:git://github.com/your/repo.git")
                    developerConnection.set("scm:git:ssh://github.com/your/repo.git")
                    url.set("https://github.com/your/repo")
                }
            }
        }
    }
    
    repositories {
        maven {
            name = "sonatype"
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = project.findProperty("sonatypeUsername") as String?
                password = project.findProperty("sonatypePassword") as String?
            }
        }
    }
}

小提示:我建议把sonatype的用户名密码放在~/.gradle/gradle.properties里,而不是直接写进build脚本。这样既安全,又方便不同项目复用。

15.3 GPG签名配置

签名这块,我曾经踩过一个坑:以为配好key就行,结果忘了指定signing.keyId,导致签名一直失败。

正确的做法是这样的:

// 在 gradle.properties 中
signing.keyId=你的密钥ID(8位十六进制)
signing.password=你的密钥密码
signing.secretKeyRingFile=/Users/you/.gnupg/secring.gpg

// 在 build.gradle.kts 中
signing {
    sign(publishing.publications["release"])
}

⚠️ 重要提醒:GPG 2.1版本之后,默认不再生成secring.gpg文件。你需要手动导出:gpg --export-secret-keys -o ~/.gnupg/secring.gpg。我当初就是被这个坑了半小时。

15.4 发布到Artifactory

如果你用的是公司内部的Artifactory,流程会简单很多——不需要签名,也不需要Sonatype那一套Close/Release操作。

publishing {
    repositories {
        maven {
            name = "artifactory"
            url = uri("https://artifactory.company.com/artifactory/libs-release-local")
            credentials {
                username = project.findProperty("artifactoryUser") as String?
                password = project.findProperty("artifactoryPassword") as String?
            }
        }
    }
}

你想想看,Artifactory其实就是一个私有的Maven仓库。你只需要把产物推上去,别人就能通过maven { url }引用了。不需要签名,也不需要审核。

15.5 完整的发布脚本示例

下面是我在实际项目中用的一个完整脚本,包含了所有必要的配置:

plugins {
    `java-library`
    `maven-publish`
    signing
}

java {
    withSourcesJar()
    withJavadocJar()
}

publishing {
    publications {
        register<MavenPublication>("mavenJava") {
            from(components["java"])
            
            versionMapping {
                usage("java-api") {
                    fromResolutionOf("runtimeClasspath")
                }
                usage("java-runtime") {
                    fromResolutionResult()
                }
            }
            
            pom {
                name.set("My Library")
                description.set("A concise description")
                url.set("https://github.com/your/project")
                
                licenses {
                    license {
                        name.set("MIT License")
                        url.set("https://opensource.org/licenses/MIT")
                    }
                }
                
                developers {
                    developer {
                        id.set("yourname")
                        name.set("Your Name")
                        email.set("your.email@example.com")
                    }
                }
                
                scm {
                    connection.set("scm:git:git://github.com/your/project.git")
                    developerConnection.set("scm:git:ssh://github.com/your/project.git")
                    url.set("https://github.com/your/project")
                }
            }
        }
    }
    
    repositories {
        maven {
            name = "sonatype"
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = project.findProperty("ossrhUsername") as String?
                password = project.findProperty("ossrhPassword") as String?
            }
        }
    }
}

signing {
    sign(publishing.publications["mavenJava"])
}

tasks.withType<Javadoc>() {
    if (JavaVersion.current().isJava9Compatible()) {
        options.addBooleanOption("html5", true)
    }
}

15.6 避坑指南

我整理了几个常见的坑,都是我自己或同事踩过的:

问题 原因 解决方案
签名失败:No secret key secring.gpg文件不存在或路径不对 gpg --export-secret-keys导出
上传401 Unauthorized Sonatype账号未激活或token过期 检查ossrhUsernameossrhPassword
Close失败:Missing POM POM信息不完整 确保pom中包含了licenses、developers、scm
Javadoc生成报错 Java 8+的Javadoc严格模式 添加options.addBooleanOption("html5", true)

我的经验:第一次发布时,建议先用./gradlew publishToMavenLocal在本地测试一遍。确认所有产物(jar、sources-jar、javadoc-jar、asc文件)都生成正确了,再往远程推。这样能省去很多来回调试的时间。

15.7 自动化发布流程

如果你经常发布,手动操作Sonatype的Nexus UI会非常烦人。我推荐用io.github.gradle-nexus.publish-plugin这个插件,它可以自动完成Close和Release操作:

plugins {
    id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}

nexusPublishing {
    repositories {
        sonatype {
            nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
            snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
            username = project.findProperty("ossrhUsername") as String?
            password = project.findProperty("ossrhPassword") as String?
        }
    }
}

配置好之后,一行命令就能完成全部流程:

./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository

嗯,这才是现代开发该有的体验。不用再手动点网页了。

小技巧:我习惯在CI/CD里配置这个流程。每次打tag的时候自动触发发布,省心又可靠。记得在CI环境里也要配置好GPG密钥和Sonatype凭据。


公众号:蓝海资料掘金营,微信deep3321