一 前言
有时候,在我们写了一个组件想将之开源给更多人分享和使用时,就需要我们发布开源组件到公开的远程仓库,如 Jitpack、JenCenter 、 MavenCentral。其中,MavenCentral 是最流行的中央仓库,也是 Gradle 默认使用的仓库之一。这篇文章就介绍如何将自己的开源组件发布到MavenCentral仓库中;
二 名词解释
2.1 何为 POM?
POM(Project Object Model)指项目对象模型,用于描述项目构件的基本信息。一个有效的 POM 节点中主要包含以下参数:
参数 | 描述 | 举例 |
---|---|---|
groupId | 组织 / 公司名 | io.github.lucasxu01 |
artifactId | 组件名 | modular-networkviewer-plugin |
version | 组件版本 | 0.0.1 |
packaging | 格式 | jar |
2.2 何为仓库(repository)
项目中需要依赖的各种各样的三方库,存放在某个位置,这个 “位置” 就是仓库。使用仓库可以帮助我们管理项目构件,例如 jar、aar 等等。
主流的构建工具都有 2 个层次的仓库概念:
-
1、本地仓库: 本地操作系统中会有一个目录用来存放从中央仓库或远程仓库下载的依赖文件;
-
2、远程仓库: 包括中央仓库和私有仓库。中央仓库是开源社区提供的仓库,是绝大多数开源库的存放位置。比如 Maven 社区的中央仓库 Maven Central;私有仓库是公司或组织的自定义仓库,可以理解为二方库的存放位置。
一方库:本工程中相互依赖的各模块;
二方库:公司内部的依赖库,一般指公司内部的其他项目发布的jar包;
三方库:公司之外的开源库, 比如apache、ibm、google等发布的依赖;
2.3 Sonatype、Nexus 和 Maven 的关系:
- Sonatype: 完整名称是 Sonatype OSSRH(OSS Repository Hosting),Sonatype是美国一家开源DevOps服务供应商,支持开源组件(中央仓),为开发者提供资源库管理器(Nexus),为开源项目提供免费的中央存储仓库服务。
- Nexus: 完整名称是 Sonatype Nexus Repository Manager,是 Sonatype 的另一款产品,用作提供仓库管理器。Sonatype 基于 Nexus 提供中央仓库,各个公司也可以使用 Nexus 搭建私有仓库;
- Maven: 完整名称是 Apache Maven,是一种构建系统。除了 Maven 之外,Apache Ant 和 Gradle 都可以发布组件。
三 新建 Sonatype 项目
发布组件到中央仓库需要 Sonatype JIRA 账号。
3.1 准备 Sonatype JIRA 账号
进入 Sonatype 仪表盘界面,登录或注册新账号:issues.sonatype.org:
3.2 新建工单
点击仪表盘面板右上角的 ”新建“ 按钮,按照以下步骤向 Sonotype 提交新建项目的工单:
填写方法总结如下:
-
项目: 使用默认选项 Community Support - Open Source Project Repository Hosting (OSSRH);
-
问题类型: 使用默认选项 New Project;
-
概要: 填写 Github 仓库相同的名称,以方便查找;
-
GroupId 组织名:
填写发布组件时使用的 groupId,后续步骤中会检查你是否真实拥有该 groupId,所以不可以随便填写,有 2 种填写方式:
- **使用 Github 账号:**按照
io.github.[Github 用户名]
的格式填写,后续步骤中 Sonatype 通过要求我们在个人 Github 仓库中新建指定名称的临时代码库的方式来做身份验证; - 使用个人域名: 按照逆序域名的格式填写,例如个人域名为
oss.sonotype.org
,则填写org.sonotype.oss
。
- **使用 Github 账号:**按照
-
Project URL 项目地址: 填写 Github 项目地址,例如:
https://github.com/lucasxu01/NetworkViewer
; -
SCM url 版本控制地址: 在 Github 项目地址后加
.git
,例如https://github.com/lucasxu01/NetworkViewer.git
。
2.3 验证 GroupId 所有权
新建项目后根据提示,点击进入工单详情页,刚新建的工单要等待 Sonotype 机器人回复,等待大概十几分钟后,在工单底部的评论区会告诉我们接下来需要的操作。
至此,Sonotype 项目准备完毕。
四 新建 GPG 密钥对
GPG(GNU Privacy Guard) 是基于 OpenPGP 标准实现的加密软件,它提供了对文件的非对称加密和签名验证功能。所有发布到 Maven 仓库的文件都需要进行 GPG 签名,以验证文件的合法性。
3.1 安装 GPG 软件
安装 GPG 软件:
# 通过 Homebrew 安装 gpg
brew install gpg
安装brew:/bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)”
验证安装是否成功:brew -v
3.2 生成 GPG 密钥对
使用 --generate-key
参数,按照指引填写相关信息和 passphrase 私钥口令。另外,使用 --list-keys
参数可以查看当前系统中生成过的密钥。
# 密钥生成命令
gpg --generate-key
# 密钥查看命令
gpg --list-keys
GPG 在生成密钥对时,会要求开发者做一些随机的举动,以给随机数加入足够多的扰动,稍等片刻就会生成完成了。完成后可以随时使用 —list-keys
参数查看密钥对信息。
解释一下其中的信息:
➜ ~ gpg --list-keys
/Users/a58/.gnupg/pubring.kbx
-----------------------------
pub ed25519 2022-09-05 [SC] [有效至:2024-09-04]
F740E820EFE033F*******************7F3C8
uid [ 绝对 ] LucasXu <xxxxx@bjtu.edu.cn>
sub cv25519 2022-09-05 [E] [有效至:2024-09-04]
# pubring.kbx:本地存储公钥的文件
# 2022-09-05 [SC] [expires: 2024-09-04]:表示密钥对的创建时间和失效时间
# LucasXu <xxxxx@bjtu.edu.cn>:用户名和邮箱
# ed25519:表示生成公钥的算法
# cv25519:表示生成私钥的算法
# F740E820EFE033F*******************7F3C8:密钥指纹 / KeyId
至此,你已经在本地生成一串新的密钥对,现在你手上有:
- 密钥指纹 / KeyId: 密钥指纹是密钥对的唯一标识,即上面
F740E820EFE033F*******************7F3C8
这一串。有时也可以使用较短的格式,取其最后 8 个字符,即***7F3C8
这一串; - 公钥: 该密钥指纹对应的公钥;
- 私钥: 该密钥指纹对应的私钥;
- passphrase 密钥口令: 生成密钥对时输入的口令,私钥与密钥口令共同组成密钥对的私有信息。
3.3 删除密钥对
有时候需要删除密钥对,可以使用以下命令:
# 先删除私钥后,才能删除公钥
# 删除私钥
gpg --delete-secret-keys [密钥指纹]
# 删除公钥
gpg --delete-keys [密钥指纹]
3.4 上传公钥
密钥对中的公钥信息需要公开,其他人才能拿到公钥来验证你签名的数据,公开的方法就是上传到公钥服务器。公钥服务器是专门储存用户公钥的服务器,并且会用交换机制将数据同步给其它公钥服务器,因此你只要上传到其中一个服务器即可。我最后是上传到 hkp://keyserver.ubuntu.com
服务器的。以下服务器都可以尝试:
pool.sks-keyservers.net
keys.openpgp.org
keyserver.ubuntu.com
pgp.mit.edu
// 上传公钥
gpg --keyserver 【服务器地址】:11371 --send-keys 【密钥指纹】
// 验证公钥
gpg --keyserver 【服务器地址】:11371 --recv-keys 【密钥指纹】
gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys F740E820EFE033F*******************7F3C8
3.5 导出密钥文件
后文发布组件的时候需要用到密钥口令和私钥文件,可以使用以下参数导出
# 默认导出到本地目录 /User/[用户名]/
# 导出公钥
gpg --export 【密钥指纹】 > xiaopeng_pub.gpg
# 导出私钥
gpg --export-secret-keys 【密钥指纹】 > xiaopeng_pri.gpg
五 配置发布脚本
完成 Sonatype 项目和密钥对的准备工作后,现在着手配置项目的 Gradle 脚本了。Gradle 提供了两个 Maven 插件:
- maven 插件: 旧版发布插件,从 Gradle 7.0 开始无法使用;
- maven-publish 插件: 新版发布插件。
这里推荐使用 vanniktech 的发布插件,gradle-maven-publish-plugin 是一个外国大佬 vanniktech 开源的 Gradle 插件,需要使用 Gradle 7.2.0 以上的 Gradle 环境。它会创建一个 publish Task,支持将 Java、Kotlin 或 Android 组件发布到任何 Maven 仓库,同时也支持发布携带 Java / Kotlin 代码的 Javadoc 产物和 Sources 产物。目前(2022/09/26)这个项目的最新版本是 0.22.0,且已经在 LeakCanary 上验证过。
5.1 vanniktech 插件环境配置
以下为配置步骤:在项目级 build.gradle
中添加插件地址,在模块级 build.gradle 中应用插件:
项目级 build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
// vanniktech 发布插件
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0'
// Kotlin Javadoc,非必须
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.20"
}
}
最新版build.gradle写法:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
// vanniktech 发布插件
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0'
// Kotlin Javadoc,非必须
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.20"
//1、依赖抓包插件库
classpath 'io.github.lucasxu01:modular-networkviewer-plugin:0.0.1'
}
}
plugins {
id 'com.android.application' version '7.3.0-beta01' apply false
id 'com.android.library' version '7.3.0-beta01' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
}
模块级 build.gradle
apply plugin: "com.vanniktech.maven.publish"
// Kotlin Javadoc,非必须。如果有这个插件,发布时会生成 Javadoc,会延长发布时间。建议在 snapshot 阶段关闭
apply plugin: "org.jetbrains.dokka"
最新版build.gradle写法::
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'monitor-plugin'
}
Sync 项目后,插件会为模块增加两个 Task 任务:
- publish: 发布到远程 Maven 仓库,默认是 Sonatype 中央仓库;
- publishToMavenLocal: 发布到当前机器的本地 Maven 仓库,即
~/.m2/repository
。
Gradle 面板:
- publish:发布到远程maven仓库;
- 发布到本地maven仓库;
5.2 vanniktech 插件发布参数配置
分别在项目级 gradle.properties
和模块级 gradle.properties
中配置以下参数。
项目级 gradle.properties
:
######################################################################
# for vanniktech
######################################################################
# 服务器地址
SONATYPE_HOST=S01
# 发布 release 组件时是否签名
RELEASE_SIGNING_ENABLED=true
# 组织名
GROUP=io.github.lucasxu01
# 主页
POM_URL=https://github.com/LucasXu01/NetworkViewer/
# 版本控制信息
POM_SCM_URL=https://github.com/LucasXu01/NetworkViewer/
POM_SCM_CONNECTION=scm:git:git:github.com/LucasXu01/NetworkViewer.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/LucasXu01/NetworkViewer.git
# Licenses 信息
POM_LICENSE_NAME=The Apache Software License, Version 2.0
POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENSE_DIST=repo
# 开发者信息
POM_DEVELOPER_ID=LucasXu01
POM_DEVELOPER_NAME=LucasXu
POM_DEVELOPER_URL=https://github.com/LucasXu01/
mavenCentralUsername=[填 Sonatype 账号名]
mavenCentralPassword=[填 Sonatype 账号密码]
signing.keyId=[密钥指纹,取后 8 位即可, 如 E4F7F3C8]
signing.password=[passphrase 密钥口令]
signing.secretKeyRingFile=[导出的私钥文件路径,如 /Users/a58/lucas_pri.gpg]
模块级 gradle.properties
中:
POM_NAME = network_viewer_plugin
POM_ARTIFACT_ID = modular-networkviewer-plugin
POM_PACKAGING = jar
POM_DESCRIPTION = The plugin used in NetworkViewer api
VERSION_NAME = 0.0.1
特别注意:私有信息不要提交到 git 版本管理中,可以写在 local.properties
中,等到要发布组件时再复制到 gradle.properties
中。而私钥文件也不要保存在当前工程的目录里,可以统一放到工程外的一个目录。
至此,所有准备工作完成。
六 发布组件到 MavenCentral 仓库
终于所有准备和配置工作都完成了!在发布之前,有必要先解释下 Sonatype 中用到的仓库地址:
6.1 仓库地址
如果你没有自定义发布的 Maven 仓库,vanniktech 插件默认会发布到 Sonatype 管理的中央仓库中。由于历史原因,Sonatype 中央仓库有 2 个域名:
按照 官方的说法 ,oss.sonatype.org 是过时的,从 2021 年 2 月开始启用 s01.oss.sonatype.org/
6.2 Staging 暂存库
细心的朋友会发现官方提供的 snapshot 仓库和 release 仓库的格式不一样,为什么呢?—— 这是因为发布 release 组件是敏感操作,一旦组件发布 release 版本到中央仓库,就永远无法修改或删除这个版本的组件内容(这个规则是出于稳定性和可靠性考虑,如果可以修改,那些本地已经下载过组件的用户就得不到最新内容了)。所以 Sonatype 对发布 snapshot 组件和 release 组件采取了不同策略:
- snapshot 组件: 直接发布到 snapshot 中央仓库;
- release 组件: 使用 Staging 暂存策略,release 组件需要先发布到暂存库,经过测试验证通过后,再由开发者手动提升到 release 中央仓库。
中央 release 仓库:"https://s01.oss.sonatype.org/content/repositories/releases"
中央 snapshot 仓库:"https://s01.oss.sonatype.org/content/repositories/snapshots"
暂存库:"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2"
6.3 发布 snapshot 组件
版本号中带 SNAPSHOT 将被视为 snapshot 组件,会直接发布到 snapshot 中央仓库。在前端发布后,立马可以在 snapshot 中央仓库搜索到。
6.4 发布 release 组件到 Staging 暂存库
版本号中未带 SNAPSHOT 将视为 release 组件,发布 release 组件后,进入 Nexus 面板查看暂存库(右上角 Log in 登录):
6.5 发布 release 组件到中央仓库
确认要发布组件后,先点击 Close,再点击 Release 即可发布:
Close 的过程会对组件进行验证,验证失败的话就会报错了。你可以直接从 Activity 面板中查看报错提示。
点击 Release右边的Drop 按钮可以删除有问题的组件:
6.6 查看已发布的 release 组件
发布成功后,有 3 种方式查看自己的组件:
- 方法 1 - 在 Sonatype Nexus 面板上查看:
- 方法 2 - 在 release 中央仓库的文件目录中查看,例如 modular-networkviewer-plugin :
- 方式 3 - 在 MavenCentral 搜索栏 查找: 这是最正式的方式,缺点是不实时更新,会有延迟,而前两种方式在发布后立即更新:
按照 官方的说法 ,发布后的组件会在 30 分钟内同步到中央仓库,但搜索功能需要达到 4 个小时。
七 发布组件到本地仓库进行测试
5.1中,我们可以再gradle面板中点击publishToMavenLocal
,将插件先发布到本地进行联调测试。
点击面板中的publishToMavenLocal
,就会将插件先发布到本地maven仓库中,默认地址一般为:
/Users/a58/.m2/repository
NetworkViewer新版AS的settings.gradle
的配置参考:
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven { url '/Users/a58/.m2/repository' }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url '/Users/a58/.m2/repository' }
}
}
rootProject.name = "NetworkViewer"
include ':app'
include ':okhttpplugin'
include ':netviewer'
本地发布和添加了依赖仓库后,NetworkViewer项目级的build.gradle
配置参考:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
// vanniktech 发布插件
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0'
// Kotlin Javadoc,非必须
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.20"
//1、依赖抓包插件库
classpath 'io.github.lucasxu01:modular-networkviewer-plugin:0.0.1'
}
}
plugins {
id 'com.android.application' version '7.3.0-beta01' apply false
id 'com.android.library' version '7.3.0-beta01' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
}
之后在app级的build.gradle
配置添加该插件就可以本地测试使用了。
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'monitor-plugin'
}
八 依赖已发布的组件
注意,已经发布到 release 中央仓库,你的工程只要包含 mavenCentral()
这个仓库地址就可以了。
repositories {
// 中央仓库(不包含 snapshot 中央仓库)
mavenCentral()
// release 中央仓库
maven { url 'https://s01.oss.sonatype.org/content/repositories/releases'}
// snapshot 中央仓库
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'}
// 暂存库,用于验证
maven { url "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2"}
}
详情可参考:NetViewer
九 寻求 Sonatype 官方帮助
如果你在使用 Sonatype 的过程中遇到任何问题,可以尝试向官方提问。一般10 分钟后就有回复了。
十 展望
Github Action:目前我们已经可以发布开源项目到 MavenCentral 中央仓库。但每次发布都需要我们手动执行 upload 任务,Github 仓库中的 Releases 面板也不会同步显示手动发布的版本记录。 我们期望的效果是在 Github 仓库上发布一个 Release 版本时,自动触发将该版本发布到 MavenCentral 中央仓库。 这需要用到 Github 提供的 CI/CD 服务 —— Github Action;
参考文章
https://blog.csdn.net/hanly_jiang/article/details/123123111
https://juejin.cn/post/7135457823055413278
https://github.com/Leaking/Hunter/blob/master/docs/README_ch.md