DEV Community

Phil Hardwick
Phil Hardwick

Posted on • Originally published at blog.wick.technology on

A Java Library Release Process Which Doesn't Suck

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)

Collapse
 
alxgrk profile image
Alexander Girke

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...

Collapse
 
philhardwick profile image
Phil Hardwick

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 a

on:
  release:
    types: [published]

I'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

Collapse
 
alxgrk profile image
Alexander Girke

Thanks for looking it up! Will hopefully test it within the next couple of weeks and let you know.