This project is supposed to serve as a sample for the (semi-)automatic release and publishing workflow to Nexus. Exemplarily, the publication to Sonatype Nexus is shown, but it can be also used with any other Nexus instance. In order to automate manual interactions on the Nexus Web UI, e.g. closing and releasing Staging Repositories, the Gradle Nexus Publish Plugin is used.
nexus-publish-example/
+-- .gradle/
+-- gradle/
¦ +-- wrapper
¦ +-- profile-release.gradle
+-- src/
+-- .gitignore
+-- LICENSE
+-- README.md
+-- build.gradle
+-- gradle.properties
+-- gradlew
+-- gradlew.bat
+-- settings.gradle
The listing above shows the project structure. Relevant for the publishing process are the following files:
- profile-release.gradle - Contains publish/release specific configuration
- build.gradle - Main build file including profile-release.gradle if release profile was selected
- gradle.properties - Properties for Gradle containing the project version
In order to be able to publish to Sonatype Nexus in particular, a couple of prerequisites must be met. At first, you have to create a Sonatype OSSHR (OSS Repository Hosting) account. Afterwards, you have to create a project ticket for claiming the group id of your artifact. For more details, please visit the Sonatype OSSRH Guide.
Furthermore, you will require a PGP key in order to be able to sign your release artifacts. For more information, please refer to the Sonatype article on Working with PGP Signatures. Summarizing the article, with GPG installed, the following steps are required:
$ gpg --gen-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
[...]
pub rsa3072 2021-03-29 [SC] [verfällt: 2023-03-29]
D724FA7DAC5AA604464E2625A2957C69C4A94806
uid ###########################
sub rsa3072 2021-03-29 [E] [verfällt: 2023-03-29]
$ gpg --keyserver hkp://pool.sks-keyservers.net --send-keys D724FA7DAC5AA604464E2625A2957C69C4A94806
gpg: sende Schlüssel A2957C69C4A94806 auf hkp://pool.sks-keyservers.net
$ gpg --list-keys --keyid-format SHORT
pub rsa3072/C4A94806 2021-03-29 [SC] [verfällt: 2023-03-29]
D724FA7DAC5AA604464E2625A2957C69C4A94806
uid [ ultimativ ] ###########################
sub rsa3072/D8FBDD6F 2021-03-29 [E] [verfällt: 2023-03-29]
$
Depending on your local GPG installation, you may have to replace gpg
by gpg2
.
In the last step of the listing, we also obtained the short form C4A94806
of the secretKeyId
for better handling in the following steps.
Afterwards, your GPG key is published and can be used for signing and verifying artifact signatures. For using it for your deployment process, you have now to export the key. This can be done by the following command:
$ mkdir ~/.gradle
$ gpg --export-secret-key C4A94806 > ~/.gradle/secring.gpg
$
During export, you have to provide the password you used while creating the key. We directly export the keyring to ~/.gradle as we will use it from there.
Finally, we have to create a file ~/.gradle/gradle.properties
with the following properties:
signing.keyId=<Your secretKeyId, e.g. C4A94806>
signing.password=<Your GPG Key Password>
signing.secretKeyRingFile=~/.gradle/secring.gpg
ossrhUsername=<Your OSSRH Username>
ossrhPassword=<Your OSSRH Password>
Now, that all prerequisites are met, we have to prepare for the first release.
Depending on what to release, this phase consists of one or two steps:
- Release the current snapshot
- Perform the publication to Sonatype Nexus
In order so release your current snapshot, e.g. assigning and tagging a version, we are first using the gradle-release plugin. Everything you need for releasing is configured inside gradle/profile-release.properties
under release{}
. There, the tag template, e.g. 'v${version}'
for tags in the format v1.0.0
, as well as source file and properties for the version number, are configured. You can trigger a release by calling:
$ ./gradlew -Prelease release
[...]
$
During this process you'll be asked for the version number of the release as well as for the next snaphot version number. However, you may let the plugin assign
version automatically by providing the -Prelease.useAutomaticVersion=true
command line switch.
After release, you are ready for publishing the released version to Sonatype Nexus.
For publishing you should checkout/switch to the tag created in the previous step unless you want to publish the current snapshot.
$git checkout tags/v1.0.0
HEAD is now at XXXXXXX [Gradle Release Plugin] - pre tag commit: 'v1.0.0'.
$
In order to trigger the publication process, execute the following Gradle command:
$./gradlew -Prelease publishToSonatype closeAndReleaseSonatypeStagingRepository
Assuming that all steps before were carried out properly, this should start the publication process including:
- Creating a Staging Repository at Sonatype Nexus
- Building and signing all artifacts of the release, e.g. library, sources and javadoc
- Creating and uploading pom.xml based on the inputs in
gradle/profile-release.properties
underpublishing {}
- Close the created Staging Repository triggering Sonatype Validation
- Release the contents of the Staging Repository
If everything worked properly, e.g. all artifacts were present, signature validation and pom.xml validation succeeded, you should be able to find your published library at Maven Central after some time.
If you want to automate your publishing process, GitHub Actions are a good option to do so. In this example we assume, that only releases are published as soon as they are created on GitHub. Those instructions are contained in file .github/workflow/publishRelease.yml
. Let's have a short look at the most important elements of this file:
on:
release:
types: [created]
This section states, that the action is triggered as soon as a GitHub Release was created. This means, you first have to perform the Release
step above before you have to create a release for the created tag manually via GitHub, which allows you to add a proper description, e.g. the changelog, and a release name. As soon as you published the release the GitHub Action is triggered for setting up its environment and finally calling the next relevant line:
run: gradle -Prelease publishToSonatype closeAndReleaseSonatypeStagingRepository
This line contains actual command which is executed by the action. As we want to perform the publication to Sonatype Nexus we just use the same command as for the publication from our local machine. But how is the execution parameterized? This is done within the next section, which is probably the most important one:
env:
ORG_GRADLE_PROJECT_sonatypeUsername : ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword : ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey : ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword : ${{ secrets.SIGNING_SECRET }}
Here you see the main difference between publishing from your local machine and via GitHub Action, which is the way on how to provide secrets, e.g. ossrhUsername
, ossrhPassword
, as well as all information required for signing your artifacts. If something goes wrong during the build process on GitHub, these elements might be good candidates for a closer look.
In order so provide secret information for the build process, GitHub offers a way to define Secrets
, which are only visible once at creation time and to the build process. To create them, navigate to your project settings and select Secrets
on the left hand side. In the upper right corner you'll find a button New repository secret
used to add a new secret consisting of Name
and Value
. The secret names can then be used in your GitHub Action definition by assigning them to environment variables available within the build context.
ℹ️ GitHub Secrets are presented to the build process as variables in the form ${{secrets.<SECRET_NAME>}}. In order to use them easily within your Gradle build process, you'll have to map them to appropriate environment variables, e.g. ORG_GRADLE_PROJECT_signingKey
. ℹ️
For this example project, the following secrets are required:
Name | Desription | Environment Variable |
---|---|---|
OSSRH_USERNAME | Your Sonatype Nexus username | ORG_GRADLE_PROJECT_sonatypeUsername |
OSSRH_PASSWORD | Your Sonatype Nexus password | ORG_GRADLE_PROJECT_sonatypePassword |
SIGNING_KEY | Your GPG ascii-armored private key | ORG_GRADLE_PROJECT_signingKey |
SIGNING_PASSWORD | Your GPG private key password | ORG_GRADLE_PROJECT_signingPassword |
If you have multiple projects to publish within one organization you may also define GitHub Secrets on organization level in order to make them available to all projects. You may also change the names of those secrets at creation time, but if you do so, you also have to change .github/workflows/publishRelease.yml
in order to assign the proper values to the required environment variables.
For now, the only remaining question is, how to obtain an ascii-armored version of your private GPG key. This can be done using the following command:
$ gpg --armor --export-secret-keys C4A94806 > ascii.key
$
Again, depending on your GPG installation you may have to replace gpg
by gpg2
.
Before the export is done, you'll be asked for the password to encrypt your private key, which is the same as you used before in ~/.gradle/gradle.properties
as property signing.password
and which you'll have to provide as GitHub Secret SIGNING_PASSWORD
. As a result, the file ascii.key
contains your ascii-armored private key which you can now provide as GitHub Secret as SIGNING_KEY
Well, that's it. Now you should be able to perform the local release (see above), create a GitHub Release based on the new tag and thus trigger the Publish process to Sonatype Nexus.
❓ While publishing I receive 'Failed to publish publication 'maven' to repository 'sonatype' [...] Received status code 403 from server: Forbidden'
❕ Check the nexusPublishing{}
section in gradle/profile-release.properties
. Using the default Sonatype settings, e.g. sonatype(), only works, if you registered at Sonatype before 24 Feb 2021. Otherwise or if your are using another Nexus instance, you have to overwrite nexusUrl
and snapshotRepositoryUrl
as stated in the file. If everything is correct, please also check ~/.gradle/gradle.properties
for containing both properties ossrhUsername
and ossrhPassword
matching the adressed Sonatype Nexus instance.
❓ I'm getting some error stating that there are signatory issues, e.g. [...]it has no configured signatory or 'WARNING: No property 'signingKey' found. Artifact signing will be skipped.'.
❕ Check the prerequisites described above. In the most simple case, you'll need a GPG key, which must be published, and the according properties inside ~/.gradle/gradle.properties
.