用Gradle在Maven Central上打包发布了第一个Java Library

最近在做一些Java上面的小试验,然后做到一半突然觉得,似乎可以把这个代码转化为一个target Java 8的库,然后就在想,是不是可以发布到Maven上面呢?这样的话,就可以在别的项目或者让其他人能够用到了呢。

另外一方面,这几年认识的朋友也都有发布过自己的仓库,所以也想试试呢。

于是首先第一步就是把原本的intelliJ生成的只有src的项目转化为符合Gradle文件结构的模样。

这一步大概就是看着别的基于Gradle的项目的文件结构来做的,具体来说有这么几个步骤吧:

  1. 创建gradle/wrapper文件夹

    1. 复制一个gradle-wrapper.jar进来

    2. 创建一个gradle-wrapper.properties,并且在里面增加必要的属性:

      1
      2
      3
      4
      5
      distributionBase=GRADLE_USER_HOME
      distributionPath=wrapper/dists
      distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
      zipStoreBase=GRADLE_USER_HOME
      zipStorePath=wrapper/dists

      这些属性是和项目本身无关的,所以直接复制过来就好。

  2. 把gradlew和gradlew.bat文件复制过来

  3. 创建settings.gradle.kts文件,并且在里面把rootProject.name设置为项目名

  4. 创建build.gradle.kts文件,并且填充必要的信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    plugins {
    id("java")
    }

    group = "com.company.example"
    version = "0.9.9"

    repositories {
    mavenCentral()
    }

    dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    }

    tasks.test {
    useJUnitPlatform()
    }

    这里需要注意的是group的名字必须和之后修改后的代码在src里面的路径相匹配,version的话,Central Portal的部署不再支持打包SNAPSHOT版本,所以需要指定一个不是SNAPSHOT的版本号。

  5. 最后一步,在src下创建main文件夹,然后创建java文件夹,再按照前面定义的group把包名路径创建出来,例如这里就应该是src/main/java/com/company/example,然后把原先src下的所有Java文件转移过去

  6. 然后reload一下Gradle的配置文件,这时候这个项目就已经被Gradle托管了。还可以进一步把test文件夹也创建了。

那么到这里就已经有了一个简单的,用Gradle来管理的Java项目。该考虑怎么打包到Maven仓库上面了。因为我用的GitHub来管理代码,所以用GitHub actions就是很自然的选择。GitHub的官方文档推荐用maven-publish插件,并且给出了很详细的介绍。

但是问题在于,这个插件给出的方法是基于OSSRH的。根据Maven Central的官方说明,OSSRH已经是legacy的接入口,所有新的注册和发布都会基于Central Portal而OSSRH仅对极个别情况开放。

我之前注册的Maven账户也是基于Central Portal的,换句话说,就是GitHub文档所推荐的办法是没法用的。

按照官方声明,官方并没有支持Gradle的基于Central Portal的发布方法。

Currently, there is no official Gradle plugin for publishing to Maven Central via the Central Publishing Portal

看起来就卡住了。

不过还好社区有不少第三方的插件,其中有几个一直有维护。在折腾了几天后我选择了gradle-maven-publish-plugin,于是就需要把build.gradle.kts给修改一下。

不过首先先在Central Portal里面拿一下用户名和密码(不是登入的时候的那个,是使用Publisher API的时候需要用到的)。打开https://central.sonatype.com然后登入进去后,在View Account里面,选择Generate User Token就可以拿到(或者刷新)一个用户名和密码了。

1
2
3
4
5
<server>
<id>${server}</id>
<username>SomeUserName</username>
<password>SomeRandomPassword</password>
</server>

大概是这样的格式。

然后去Namespaces里面,需要去claim一个namespace,这里会需要用到自己名下的域名,得去DNS提供商那边在@下面加一行TXT记录。然后等待验证就行了。

Central Portal弄好了之后,就得更新项目文件了。有两个文件需要更新,一个是刚才提到的Gradle的kts配置文件,另一个是GitHub Actions的配置,用以在需要的时间点触发workflow。

首先是Gradle这边,先在plugins里面增加一行:

1
2
3
4
5
6
import com.vanniktech.maven.publish.SonatypeHost

plugins {
id("java")
id("com.vanniktech.maven.publish") version ("0.30.0")
}

这里需要注意一个有点微妙的地方:我的项目JDK版本是1.8的,但是Gradle也依赖Java JDK,而不同版本的Gradle和Gradle所引用的插件,需要的JDK版本是不一样的。如果我在1.8下面,在引入这个新插件后,build.gradle.kts会出现语法错误。

这时候只需要在Settings - Build, Execution, Deployment - Build Tools - Gradle里面,把这个项目的Gradle JVM改为较新的版本,例如IDEA自带的jbr-17,重新reload一下,就可以了。

为了保证项目输出的代码版本,也可以添加这么一行:

1
2
3
4
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

加了新插件重新刷新依赖后,就可以增加打包相关的配置了。

我这里在配置文件最后加上的部分大概是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
signAllPublications()

coordinates("com.company.example", "library-name", "0.9.0")
pom {
name.set("Full name of Library")
description.set("Some descriptions")
inceptionYear.set("2024")
url.set("GitHub仓库链接")
licenses {
license {
name.set("BSD-3-Clause license")
url.set("https://opensource.org/license/bsd-3-clause/")
distribution.set("https://github.com/path/to/the/LICENSE.md")
}
}
developers {
developer {
id.set("作者")
name.set("name")
url.set("https://github.com/作者")
}
}
scm {
url.set("GitHub仓库链接")
connection.set("scm:git:git://github.com/author/repo.git")
developerConnection.set("scm:git:ssh://git@github.com/author/repo.git")
}
}
}

其实这个也是插件的官方文档给出的样例。

然后需要用到GPG生成的私钥、Passphrase和短ID,这里就不详细介绍了。

结合之前的Central Portal的用户名密码,一共是五个变量。

在GitHub的仓库设置里,Security - Secrets and variables - actions - Repository secrets里面,加入这五个变量。

然后就是定义GitHub actions的配置了,需要在根目录下创建.github/workflows文件夹,并且在里面创建一个yml文件,例如release.yml。

大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: Publish package to Maven Central Portal
on:
pull_request:
types:
- closed
jobs:
publish:
if: github.event.pull_request.merged
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

- name: Publish Artifacts
run: ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIG_KEY_ID_SHORT }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIG_KEY_PASSPHRASE }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIG_KEY_PRIVATE_KEY }}

这里的env下面的五个变量刚好是我们刚才放进GitHub仓库设置里面的五个变量。

根据插件的官方文档的Secrets栏目,这五个变量的变量名是固定的,也就是ORG_GRADLE_PROJECT开头的五个固定的名字。

然后我这里的话,设定为每次合并PR后会运行一次打包release一个版本,所以我设置成这样:

1
2
3
4
5
6
7
8
on:
pull_request:
types:
- closed

jobs:
publish:
if: github.event.pull_request.merged

修改on的内容就可以按照不同的需求去决定workflow的运行时机,例如on: [push]会在每次push被触发的时候运行一次这个job。按照不同的需求,就可以组合出很复杂的流程,例如引入一些简单的测试。jobs.publish.if限制了这个流程只能在github.event.pull_request.merged的值为true的时候才被运行,换句话说,就是只有被合并了的PR可以触发workflow。

然后按照平常一样修改代码,开PR,合并PR后,再登入Central Portal,就会发现有一个Deployment被推送上来了。这时候就可以选择Publish,把它发布出来,或者Drop掉它。如果选Publish的话,十几分钟后,就可以在Maven Central里面搜到这个新发布的Java库了。

大概就是这样了。

于是也就发布了我的第一个Maven仓库了呢(撒花)


用Gradle在Maven Central上打包发布了第一个Java Library
http://inori.moe/2024/11/23/publish-a-java-lib-to-maven/
作者
inori
发布于
2024年11月23日
许可协议