如何发布Jar包到Maven Central Repository

太长不读篇

  1. issues tracker 上注册
  2. 创建 issues
  3. 配置 build.gradle
  4. gpg 生成 key pair 以便签名
  5. 上传 Release Archive
  6. 关闭并验证 Staging 环境的 Archive
  7. 发布 Archive
  8. 通知 issue 管理员开启同步

细读篇

1. 注册

Maven Central 网站并不提供注册的功能,你需要到 Sonatype 网站上进行注册。而事实上,Sonatype 网站也没有直接提供一个注册链接。真正的注册入口在 issues tracker 上。一旦完成注册后,你需要创建包含待发布包信息的 issue。

2. 创建 issue

创建 issues

在 Sonatype 的 dashboard 上点击创建按钮,根据弹出框的提示,填写简介、描述、GroupId、Project URL、SCM url 以及你在 jira 上的用户名。创建完毕后,会被自动跳转到该 issue 的详情页并分配一个唯一的ID,如:OSSRH-33944。余下的时间只需要等待,一般在两个工作日之内,Sonatype 的工作人员就会着手处理,然后他会在该 issue 底下的评论区留言。

创建成功的 issue

如果代码是托管在 github 上,按照惯例,GroupId 应该取 github 上的域名,比如:com.github.qianyan。不过,这里我预备上传的包的 GroupId 是 com.lambeta,这是我购买的域名。审核者对此有所顾虑,所以很贴心地留言如下:

Do you own the domain lambeta.com? If not, please read:
http://central.sonatype.org/pages/choosing-your-coordinates.html
You may also choose a groupId that reflects your project hosting, in this case, something like io.github.qianyan or com.github.qianyan

在回复这个域名确实为我所有之后,工作人员就贴出不同环境的仓库地址。

Configuration has been prepared, now you can:
Deploy snapshot artifacts into repository https://oss.sonatype.org/content/repositories/snapshots

Deploy release artifacts into the staging repository https://oss.sonatype.org/service/local/staging/deploy/maven2
Promote staged artifacts into repository 'Releases'
Download snapshot and release artifacts from group https://oss.sonatype.org/content/groups/public
Download snapshot, release and staged artifacts from staging group https://oss.sonatype.org/content/groups/staging
please comment on this ticket when you promoted your first release, thanks

最后一句很重要,说的是,当我第一次正式发布的时候,需要留言告知工作人员,以便他们开启中央仓库的同步,这样我的包才会在 Maven Central 仓库中可见。

3. 配置项目的 build.gradle

拿到仓库地址,我们就需要在自己的项目中进行一些必要的配置,包含:jar、sourcesJar、javadocJar 以及对这些产物的 signing(签名)。

maven 插件

apply plugin: 'maven'

maven 插件提供了 uploadArchives task,我们需要在这个 task 中配置仓库地址,以及 pom 的相关信息,因为上载到 maven 仓库的包必须要有 pom 文件,否则无法查找或被依赖。具体配置如下:

uploadArchives {
    repositories {
        mavenDeployer {
            beforeDeployment { deployment -> signing.signPom(deployment) }

            repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
                authentication(userName: ossrhUsername, password: ossrhPassword)
            }

            snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots") {
                authentication(userName: sonatypeUsername, password: sonatypePassword)
            }

            pom.project {
                name project.name
                packaging 'jar'
                description 'underscore string in java'
                url 'https://github.com/qianyan/underscore.string.java'

                scm {
                    url 'https://github.com/qianyan/underscore.string.java'
                    connection 'https://github.com/qianyan/underscore.string.java.git'
                    developerConnection 'git@github.com:qianyan/underscore.string.java.git'
                }

                licenses {
                    license {
                        name 'MIT Licence'
                        url 'https://raw.githubusercontent.com/qianyan/underscore.string.java/master/LICENSE'
                        distribution 'repo'
                    }
                }

                developers {
                    developer {
                        id 'lambeta'
                        name 'Yan Qian'
                        email 'qianyan.lambeta@gmail.com'
                    }
                }
            }
        }
    }
}

对应生成的 pom.xml 大致如下:

<name>underscore.string.java</name>
<description>underscore string in java</description>
<url>https://github.com/qianyan/underscore.string.java</url>
<licenses>
<license>
  <name>MIT Licence</name>
  <url>https://raw.githubusercontent.com/qianyan/underscore.string.java/master/LICENSE</url>
  <distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
  <id>lambeta</id>
  <name>Yan Qian</name>
  <email>qianyan.lambeta@gmail.com</email>
</developer>
</developers>
<scm>
<connection>https://github.com/qianyan/underscore.string.java.git</connection>
<developerConnection>git@github.com:qianyan/underscore.string.java.git</developerConnection>
<url>https://github.com/qianyan/underscore.string.java</url>
</scm>

注意 authentication(userName: ossrhUsername, password: ossrhPassword)authentication(userName: sonatypeUsername, password: sonatypePassword),这里的用户名和密码其实就是在 Sonatype 上注册的用户名和密码。为了让 gradle 脚本顺利执行,需要在当前工程下的 gradle.properties 文件中设置对应的变量,如下:

sonatypeUsername=
sonatypePassword=
ossrhUsername=
ossrhPassword=

这份文件会作为源代码的一部分提交,所以聪明的我们不会傻傻地把自己的用户名和密码 push 到 github 上面。和大部分 *nix 系统上的工具类似,gradle 也有本地配置,我们可以新建一份 gradle.properties 文件到 ~/.gradle/gradle.properties,然后把用户名和密码写入其中。这样,实际运行时,本地配置就会覆盖项目下对应的这些变量值。

4. 设置 gpg 以签名 Archive

gpg 生成的 key pair 主要是供签名使用的。假定本机已经安装 gpg,首先使用 gpg 生成 key pair。

$ gpg --gen-key

然后,查找你的 keyId:

$ gpg --list-keys
# ->
pub   2048R/XXXXXX 2017-09-14 [expires: 2018-05-14]
uid       [ultimate] Yan Qian (lambeta) <qianyan.lambeta@gmail.com>

其中 XXXXXX 就是你的 keyId。接下来必须发布你的公钥:

$ gpg --keyserver hkp://pgp.mit.edu --send-keys XXXXXX

验证公钥已经发布成功:

$ gpg --keyserver hkp://pgp.mit.edu --search-keys qianyan.lambeta@gamil.com # user email address

当然,上述操作都可以使用 gpg tools 在 UI 上完成。

signing 插件

完成上述步骤之后,我们需要在 build.gradle 中添加 signing 插件及其配置,如下:

apply plugin: 'signing'

signing {
    required { gradle.taskGraph.hasTask("uploadArchives") }
    sign configurations.archives
}

上面的配置明确了 gradle task 的 DAG 中必须含有 uploadArchives,之后针对 archives 进行签名。

signing 插件如何同 gpg 生成 key pair 交互呢?这就需要在~/.gradle/gradle.properties再声明三个变量,如下:

signing.keyId=XXXXXX
signing.password=your_key_pair_password
signing.secretKeyRingFile=/Users/your_name/.gnupg/secring.gpg

还剩下最后的一步,归档 Jar,sourceJar(源代码)和javadocJar(API 文档),这些是需要签名的对象。

archive

apply plugin: 'java'

task sourcesJar(type: Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}

task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from 'build/docs/javadoc'
}

artifacts {
    archives jar
    archives sourcesJar
    archives javadocJar
}

归档产物最终都会被签名,生成以 .asc 为后缀的签名文件。

build/libs
├── underscore.string.java-0.0.1-javadoc.jar
├── underscore.string.java-0.0.1-javadoc.jar.asc
├── underscore.string.java-0.0.1-sources.jar
├── underscore.string.java-0.0.1-sources.jar.asc
├── underscore.string.java-0.0.1.jar
└── underscore.string.java-0.0.1.jar.asc

这些产出物在最终发布的时候,需要经过验证,如果验证失败,比如:缺少 javadoc 或者某个 *.asc 文件,则不被允许发布。

5. 上传 Release Archives

根据的 maven 的标准,日常开发我们会使用 snapshot 版本,如:0.0.1-SNAPSHOT;发布时,去掉后缀-SNAPSHOT,即:0.0.1。而 maven 会根据这个特点,机智地辨识是上传到 snapshotRepository 还是 releaseRepository 的。因为我们得发布包,所以修改版本为 0.0.1 之后,我们只需要简单地执行如下命令:

$ gradle uploadArchives # or `gradle uA` this is a shortcut.

6. 关闭并验证 Staging 环境上的 Archive

staging Repo

登录 Sonatype 的 Nexus Repository Manager,然后点击左边侧边栏的 Staging Repositories,搜索comlambeta (GroupId 去掉中间的'.')。接下来查看 Content tab,重点检查 pom 或者签名文件是否遗漏!当确认无误后,即可关闭 (Close) 这个 Repo。关闭过程中,Nexus 会逐项检查产物是否合规,如果出现验证错误,则在 Activity tab 中显示具体失败的步骤及原因。

7. 发布 Archive

如果上面的验证通过,上面本来不可用的 Release 按钮会变为可用。点击 Release 按钮,直接发布包。

8. 通知 issue 管理员开启同步

发布包之后,就可以通知管理员开启同步。我在原来的 issue 的评论区留言:

I have a first release version 0.0.1 for this library.

很快地,管理员就回复同步已经开启:

Central sync is activated for com.lambeta. After you successfully release, your component will be published to Central, typically within 10 minutes, though updates to search.maven.org can take up to two hours.

不过,由于当时所用 gradle2.1 的版本,导致了上传时 pom 文件被遗漏,在 search.maven.org 中搜索不到。管理员很热心地解释了这个现象:

search.maven.org needs a valid POM file to be a part of your uploaded artifacts. Browsing Maven Central directly:
http://repo1.maven.org/maven2/com/lambeta/underscore.string.java/0.0.1/
it appears that a POM file is missing.

遂升级到 3.1 版本,重新上传之后就能在 search.maven.org 中看到。

9. 检查同步成功

除了通过 search.maven.org 检查同步是否成功之外,查询mvnrepository也是常用的搜索方式。不过,值得一提的是,mvnrepository 相较于 search.maven.org 同步会更慢点,原因是 mvnrepository 引用了 central.maven.org 仓库。而 central.maven.org == repo1.maven.org,两个域名对应的 IP 是一样的,而这个 repo1.maven.org 就是默认的 Maven central repository,也就是 search.maven.org 的仓库。
所以,你可以在以下两个仓库看到发布包:
http://central.maven.org/maven2/com/lambeta/underscore.string.java/
http://repo1.maven.org/maven2/com/lambeta/underscore.string.java/


参考链接
[1] simple library publishing with gradle
[2] release deployment
[3] ossrh guide

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容