When your project is hosted on Github, it is a natural idea to have continuous integration via Github Actions, which is a really nice system for that with loads of predefined action components you can use for that. And, if you like, you can go even a step further, and release your code from Github Actions to maven central just with a click of a button. That's what I did (you can see our workflows here ), and I'd like to share some insights, tricks and stumbling points I experienced.
Creating a Release with maven and Github Actions
The maven release plugin basically works in two phases:
- you prepare the release with
mvn release:prepare, which does a number of checks, then switches the version numbers in your pom.xml, creates a tag and pushes that to your git repository
- you create the release with
mvn release:perform, which checks out the code freshly in a new directory target/checkout in your root project, compiles it there, creates the javadoc and sources JARs, GPG signs them, and uploads that to whereever you deploy your relelease (e.g. Sonatype OSS Repository hosting in our case).
An awful lot in just two steps, right? In fact, that does have the disadvantage, that if something fails in one of those steps, you'll be left with traces in the form of a tag and the release commits in your Git repository, and either have to ignore that or delete that stuff by hand (which seems an a bit improper thing to do). Right, there is
mvn release:rollback , but that leaves even one more commit in your git history.
But as it turns out, you can separate some steps out, pretty much to a point that a failed attempt will leave no traces except that failed Github Actions run you can examine.
Delaying the persistent actions during the release
It took a while, but you can see the final result in createrelease.yml, which we use for almost all of our projects on Github. Never mind that it's pretty long - I left a couple of steps in there that can help diagnosing when something went wrong. That's not necessary, but often helpful since on Github Actions you can't easily inspect the workspace after a failed run, like you can do e.g. on Hudson / Jenkins. So you'll probably end up running the workflow again and again with some debugging statements in there. The important points are here what helps us delay the irreversible actions until all the steps that are likely to fail are already run:
- Checking out the code, setting up the build environment and initializing GPG keys, configure Git etc.
- Prepare the release with
mvn release:prepare -DpushChanges=false. That creates those release commits, but does not yet push that into your Github project.
- Perform the release with
mvn release:perform -DlocalCheckout=true -DdeployAtEnd=true, but take care to configure your nexus staging maven plugin with autoReleaseAfterClose=false . That way, the release is uploaded to OSSRH only after the build is mostly finished, but not yet released to the maven central repository, since we haven't pushed the commits yet.
- Push the release tag and commits into the Github project.
- In the directory target/checkout release to the maven central repository with
- If anything failed, do a
mvn nexus-staging:drop, instead.
With that annoyingly elaborate ordering of steps, the likelihood that you have permanent traces because of a failed build is pretty low. Only from step 4 you have permanent changes, and once you've got that script run once, these steps will probably work every time.
The OSSRH documentation gives you a pretty good walk through to deployment, but there were some interesting stumbling points on the way when running with Github Actions.
- The GPG keys have to be encoded with base64 before being put into the Github project secrets, since they otherwise span multiple lines, which creates trouble when ijmporting. You also have to import the "owner trust" for that key.
- Setup permissions in Github Actions to push to Github
- For whatever weird reason, we had to run
mvn install javadoc:aggregate site:aggregateinstead of just
mvn site:aggregatewhen creating the site, but that's from a different script (master.yml).