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?
Requirements
I had some strict requirements for this release process:
- no multiple commits
- no commits by CI
- should build snapshot versions when someone is building locally
- should build snapshot versions as a default when building in CI so not every push to master is a “release”
- versioning should be by semver, and shouldn’t include build numbers
- shoudn’t be tied to the CI choice (i.e. no CircleCI specific release)
New solution
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.
How it works
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.
How to add it to a project
1. Add the semver git plugin
plugins {
id "io.wusa.semver-git-plugin" version "2.2.1"
}
2. Configure the plugin
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}" }
}
}
}
3. Use the version
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"
4. Set up your CI to build when a new tag appears
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
Github Bonus
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.
Conclusion
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…
Top comments (3)
Love your approach. Have you already tested it with other CI providers? Asking because I don't know if there is something similar to "tag_filter" for e.g. Github Actions or CircleCI...
Yeah great question - CircleCI can do it via their
filters: tags:
config which accepts regex (circleci.com/docs/2.0/configuratio...) and Github (docs.github.com/en/actions/referen...) have aI'm not sure if you have to create the "release" through the Github UI for this to work (which I believe just tags the repo) or whether Github recognising a tag as a release (as I mentioned in the post) will simply start the workflow.
Let me know if you try it out! Would be interested to hear how it goes
Thanks for looking it up! Will hopefully test it within the next couple of weeks and let you know.