We have spent quite some effort to fully automate our release processes with github actions. This documentation aims to explain how it works.
Usage is very simple:
-
Every night a SNAPSHOT release is build and deployed for internal developers and retesting of bugfixes. This process if fully automated. See testing-snapshot-releases for details.
-
When all is prepared for the next release, we run the release workflow. With according permissions, you will see a gray
Run workflow
button that opens an overlay with a greenRun workflow
button that you need to press (correct branchmain
is preselected). That is all you need to create a new release. It will automatically appear on github releases. Please note that the staging process of the release to maven central has some delay so the download links may only work ~1h after the release job completed.
After an official release has been published the following manual steps need to be performed:
-
Close the release milestone that has just been released on github (see milestones and pay attention to the links
Edit
,Close
, andDelete
). -
Verify and potentially edit/update the new
SNAPSHOT
version in maven.config. The release workflow will always increase the last digit (micro-version) and often we instead want to increase the month segment. Example: the version was2025.01.001-SNAPSHOT
and therefore the release2025.01.001
has been build and deployed. Then the new version was set to2025.01.002-SNAPSHOT
. In case that the January is almost over and the next release is planned for Februrary, you instead want to change the version to2025.02.001-SNAPSHOT
. -
Finally, you edit our CHANGELOG and add the next planned version at the top. Include the header and footer lines but leave the content in between as a blank line (where the issues will be added as bullet-list).
To make all this work a lot of different technical aspects work together. Here we consider fundamental things such as Java, Maven, and Github Actions as given.
-
As a preparation we have requested OSSRH (Open Source Repository Hosting) for our project.
-
We have created according account and registered PGP key
-
We have added PGP key and account credentials to our github secrets (see also our settings.xml that is referenced from maven calls in github actions).
-
In our parent POM we have configured a deploy profile with additional steps for deploying via OSSRH
-
In order to make our CLI tool work fast and without having Java installed as a pre-requisite, we use GraalVM native image technology to compile our Java code to a platform specific binary.
-
To build such a binary, we have configured a native profile.
-
This uses the native-maven-plugin to actually build the native binary.
-
Since GraalVM does not support cross-platform compilation, we need to run such build on a (virtual) machine operated by the according OS.
-
The awesome github platform supports arbitrary operating systems and architectures to run builds on from github actions.
-
Therefore we run a job (
build-natives
) using astrategy
withmatrix.os
configured to all the operating systems and architectures we support. The configured values are container image names that match to a specific platform but are a little cryptic. You can find a list of these container images here. -
This will spawn a build for each configured OS in a container on the according OS and architecture. All these builds will run in parallel.
-
We use the
setup-graalvm
action to make GraalVM available in the build container. -
To build the actual native image, we simply invoke
compile
goal via maven but only in thecli
module (cd cli
). Here we need to activate thenative
profile (-Pnative
). Also we can skip the tests since they are done in the main build anyway (-DskipTests
). Since we want to build the native image for a specific version, we compute that version (e.g. by removing the-SNAPSHOT
suffix in case of an official release) and override it viarevision
variable (-Drevision=${…}
). -
Only after all these spawned builds have completed successfully the main build continues with the next job (
release
ordeploy
) what we call the "Main Build". -
In order to use the native image(s) that have been build on different machines, we use the upload-artifact action to upload them to an github exchange store and download-artifact action to get them all into the main build.
-
Like with any maven project, we use the
deploy
goal via maven to compile, test, package, install and finally deploy our project. -
Here, we build the entire project with all modules including the documentation that gets generated as PDF (see docgen and documentation/pom.xml for details).
-
Since the documentation PDF gets attached as artifact it is installed and deployed to the maven repository.
-
In the
cli
module the build we compile our code again withjavac
and run the automated tests. -
However, the real compilation to the native image has already happened before (see above section).
-
Content that is rather static like script files can be found in src/main/package.
-
We use a specific resource configuration to filter and copy such files to
target/package
. -
Therefore, the main step here is the
Assembly
creating an release for every target platform (called by maven with-Passembly,deploy
). -
Finally, all artifacts are signed, installed and deployed to OSSRH nexus.
-
In case of an official release they are automatically staged to maven central and also published to our github releases using the GitHub CLI (
gh release create
). -
Also only for official releases, we also write changes to our version (see maven.config) and commit them.
-
The bumped release version also is stored as annotated tag via git.
-
Also the next
SNAPSHOT
version is set and committed. -
After all was successful we push our commits and the tag - in case the build failed, nothing will be pushed and commits will be lost.
-
In order to build a
.tar.gz
archive with all the content needed in a release, we have configured a assembly profile. -
This uses the maven-assembly-plugin to build such compressed archive.
-
For each platform (OS and architecture), we have an according configuration file in src/main/assembly.
-
The assembly descriptor file format is described here.
-
Each such file includes the according native image. Therefore, the proper container image name from the
matrix.os
(see above) has to be referenced (see here for an example). -
Additionally we reference the package content (see above) and configure exclusions to ensure that only content relevant for the according platform gets included (e.g.
*.bat
files are only included in Windows releases but not for Linux or Mac). -
Also the configuration includes the documentation as PDF (see here).
And finally we put it all togehter as github action workflow:
-
release.yml is the workflow for an official release.
-
nightly-build.yml is the workflow for a SNAPSHOT release.
-
For the nightly-build we use a trick to skip the build if no changes happened to our git in the last 24h to avoid waste: We created another check-for-updates.yml workflow that runs every night and checks for such updates. Only if recent changes where pushed to git on
main
, thenightly-build
job is triggered and otherwise the build ends without any further action.
Both release
and nightly-build
workflow use the workflow_dispatch
trigger allowing them to be run manually as described above.
However, the nightly-build
is typically only triggered from check-for-updates
workflow automatically.
But for testing some change with GraalVM specific behaviour during the day, we sometimes also trigger the nightly-build
workflow manually.