August 07, 2020
Everytime I read about Maven’s release process it makes me groan. There must be a better way than this. Something less manual, with less commits and less steps, right?
I had some strict requirements for this release process:
The best, simplest way of releasing Java apps I’ve found is via git tags and a lesser known Gradle plugin I’ve recently discovered, so I’ll show you how I’m now releasing Java libraries without the pain.
The current version of the project is determined by the most recent git tag on the branch. So if the last tag was 0.0.5
, 2 commits ago, and you’ve configured the plugin to increment the patch version, it’ll be 0.0.6-SNAPSHOT
. If you merge that commit to master, CI will also build 0.0.6-SNAPSHOT
and publish it to your Maven repo so others can try it out. If you want to release that commit as a new version, just tag the commit with the version you want e.g. 0.0.6
. CI will pick up the new git tag and re-build. It will see that the current commit has a semantically versioned tag and it will use that as the version of the project. You can also tag it with any version number e.g. 0.1.0
or 1.0.0
. This keeps the developer in control of how the versions increment.
plugins {
id "io.wusa.semver-git-plugin" version "2.2.1"
}
This is the simple configuration I decided on: that all branches behave the same way (incrementing the patch version when a tag isn’t on the current commit) and that the version is just major.minor.patch
with a possible -SNAPSHOT
suffix. More options are available as shown in the plugin README.md.
semver {
snapshotSuffix = "SNAPSHOT"
initialVersion = "0.0.1"
branches {
branch {
regex = ".+"
incrementer = "PATCH_INCREMENTER"
formatter = { "${semver.info.version.major}.${semver.info.version.minor}.${semver.info.version.patch}" }
}
}
}
Assign the version calculated by the plugin to the version of the project. I explicitly call .toString()
here so it’s only evaluated once. If you have a multimodule build, the version will be evaluated for each subproject and the .toString()
method will be called each time (which is where all the git commands and tag searching happens).
I also find it very useful to output the version being built for visibility.
version = semver.info.toString()
println "Building version $version of $rootProject.name"
This will be different based on what CI you use, but in Concourse I added a new git resource with a tag_filter: *.*.*
and then added a new input to the build job which will also trigger the job:
resources:
- name: commons-utils.git-release-tags
type: git
check_every: 2m
source:
uri: git@github.com:PhilHardwick/commons-utils.git
branch: master
private_key: #private key config
fetch_tags: true
# only detect a new version when tag matches the semver pattern
tag_filter: "*.*.*"
jobs:
- name: build-commons-utils
plan:
- get: commons-utils.git
trigger: true
params:
fetch_tags: true
# use new resource as input and trigger job on new releases
- get: commons-utils.git-release-tags
trigger: true
As a bonus, if you’re using Github, it will also pick up the tags and show them as releases on your Github repository so developers can easily check the current version.
I’m a big fan of this release process since it is simpler than the usual Maven releases process with multiple commits. It’s also not tied to any CI implementation, and it works the same in CI as it does locally.
What’s next? Still need to find a good way to do release notes! Maybe using conventional commit…
Written by Phil Hardwick