In this post I’m describing my current setup regarding releasing a versioned library into Maven central repository.
Update: see latest in v2
Initiating the release
The release starts from the default branch. There shouldn’t be any
pending changes. The version in the
pom.xml must be a snapshot version.
To trigger the release, I create and push a new branch named
x.y.z is the version that I want to release. This branch pattern
is picked up by a dedicate GitHub Actions workflow which does the work.
To create and push the branch, I use a Python script (
I also to perform and finalize the release. So for this first step, I run
./scripts/release.py initialize --version x.y.z.
Performing the release
The release is done through a GitHub Action. The custom
does all the work, which is a lot:
- Determine the release version. It checks the environment variables
GITHUB_REF_TYPE, which must be set to
GITHUB_REF_NAME, which must follow the pattern
- Import a GPG key. In order to publish to the central Maven repository,
there’s a process which I have long forgotten (not as easy as publishing
to npm), which involves having your own GPG key. I store the key as a file
in the repo in a secure way. To import and use the key, I need to configure
two secrets in GitHub:
GPG_PASSPHRASE. This also needs the
gpgbinary to be present on the system.
- Configure git identity. Since we’re going to be making a few commits,
gitneeds to know who we are. The script just runs
git config user.name "My name"and
git config user.email "My email".
- Preparing the release. This uses the Maven release plugin and runs the
release:preparegoal. I am passing
-DreleaseVersion=x.y.zto use the version I indicated when I created the release branch. Also,
-DpushChanges=false, because the plugin has some difficulty pushing when running through GitHub Actions which I didn’t want to dive into. When this step finishes, there are new commits on the current branch and a new git tag. The first commit switches from snapshot to release version (e.g. from
1.2.3) and then the next switches to the snapshot version of the next iteration (e.g.
- Update the changelog. I use git cliff to automatically generate a changelog stored in the
git repo as
CHANGELOG.md. This is a good opportunity to do so, as we just got a fresh tag and we haven’t pushed the changes yet. I install git cliff in an eariler step of the GitHub Action by curling the latest release and extracting it somewhere in the
PATH. Another important point, for git cliff to work, the checkout action needs to fetch all commits with
fetch-depth: 0, otherwise the changelog will be empty.
- Push changes to the current branch. This is apparently supported out of the box in GitHub Actions,
a pleasant surprise. It also doesn’t result in an some infinite loop of workflows being executed.
git push --follow-tags(to also push the tag created by the Maven release plugin).
- Perform the release. This uses again the Maven release plugin but this time runs the
release:performgoal. The script creates dynamically a Maven
settings.xmlfile which contains my credentials for the Maven central repository server (stored also as GitHub secrets) and the GPG secrets as properties
gpg.passphrase. The server id in the
settings.xmlneeds to match the server id in my
distributionManagementsection, as well as the
serverIdproperty. I pass
releaseplugin so that it doesn’t attempt to clone anything from GitHub.
- Clean up: delete the GPG key.
Finalizing the release
Meanwhile, on my computer, I just wait for the pipeline to finish. I get
an e-mail from Sonatype saying that it has analyzed my release and hasn’t
found any vulnerabilities. At this point, my release is done. I need to
merge the release branch and delete it. I use the same
for it and run
Show me the code
- release.py The custom Python script to orchestrate all the above.
- release.yml The GitHub Action workflow.
Some of the complexity probably comes from Maven’s notion of snapshot and release versions, but I wanted to follow that convention.
One obvious problem is that it’s very easy to make a mistake in the version, e.g. push a branch like
release-30.0.0 instead of
Furthermore, this probably only works for a team of one. It’s fine for me and my hobby
project, but if you have two people, it doesn’t offer any protection from two people
accidentally trying to create a release (one person thinking they should be pushing version
as a hotfix and another person thinking they should be pushing version
1.3.0 as a minor version).
Essentially, the choice of the version number is outside the approval process. The person who initiates the release can do so if they have the right to push a branch, without double checking
with someone else.