The Trouble With Maven Release Plugin

In a previous post I talked about what the maven-release-plugin does and explained how a typical mvn release:clean release:prepare release:perform command actually works.

Last week I had to integrate a maven project I had written into a GitLab CI environment. Up until then I had been cutting releases using the Maven release plugin locally on the command line.

The workflow is something like this:

  • Make sure there’s no uncommitted source code.
  • Clean out the working directory of any temporary release files (like backup poms, etc) and then get to work on the actual release.

Remember, performing a release should be nothing more than cutting SNAPSHOT releases into solid releases, committing this change, and tagging it with the solid version. You can then build and release artifacts from this commit if necessary.

As a quick recap, mvn:perpare makes the solid release and promotes the pom to a new SNAPSHOT version. You can then compile and ship the artifact by running mvn release:perform which will release the 1.0 code.

When integrating with CI this is what we’d like to happen

  • Developer commits a new feature to master (or makes a merge request for an entire feature to master).
  • Continuous integration builds and tests the code.
  • Upon succesful build, the pipeline would ask “do you want to release?”

If you say yes, then the build job should cut, tag and push the release commit. You then increment the project version to a SNAPSHOT and commit this so developers can continue working. In total, two additional pushes have occured. As a result of this tag being pushed, the CI system triggers another pipeline which is responsible for building the project to generate the artefact and then uploading the artefact to the release area and generating a release on the releases page. The second build makes sure the new SNAPSHOT build works fine.

This all sounds great, but when you try to actually build this workflow in practice by using something like gitlab, you quickly find that the implementation details of the release plugin cause problems with the pipeline creation.

But why? Here’s the logs:

01 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git commit --verbose -F /tmp/maven-scm-51222345.commit pom.xml project-common/pom.xml project-silent/pom.xml project-slack/pom.xml project-server/pom.xml project-test/pom.xml project-application/pom.xml project-installer/pom.xml
02 [INFO] Working directory: /builds/pet-projects/project
03 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git symbolic-ref HEAD
04 [INFO] Working directory: /builds/pet-projects/project
05 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git push refs/heads/master:refs/heads/master
06 [INFO] Working directory: /builds/pet-projects/project
07 [INFO] Tagging release with the label project-1.17...
08 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git tag -F /tmp/maven-scm-67289022.commit project-1.17
09 [INFO] Working directory: /builds/pet-projects/project
10 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git push refs/tags/project-1.17
11 [INFO] Working directory: /builds/pet-projects/project
12 [INFO] Executing: /bin/sh -c cd /builds/pet-projects/project && git ls-files
13 [INFO] Working directory: /builds/pet-projects/project

If we look closely at the logs, we can see that for the initial cut, mvn release actually does two pushes instead of once. The first is after the untagged commit containing the solid release pom, the second is after it has been tagged to push the tag upstream. Remember every push will result in a pipeline trigger. The effect of this is that two release pipelines appear, with one of them cancelled. This is the way the plugin has been implemented and there’s nothing that can be done (short of changing the plugin’s implementation)

As a result, the operation of mvn release:prepare is incorrect in the context of a CI system. release:prepare is the most useful part of the release plugin as it is the part that actually does the work of manipulating the pom and scm, so not being able to invoke it renders the plugin almost useless.

What is truely absurd here, is that the maven-release-plugin doesn’t actually do anything with commits; it delegates the whole process to the scm plugin, which is quite capable of committing without pushing. I’m sure I’m not the only person who has encountered this problem, and having read the documentation from top to bottom and found no additional flags to solve it, I assume the folks at mvn won’t fix it.

The alternatives are:

  • Reimplement a better maven relase plugin
  • Contribute a fix to the existing one.
  • Or just write the rather short set of instructions directly in .gitlab-ci.yml

Here’s the equivalent behaviour in gitlab CI (as a replacement for the maven-release-plugin

  #release the current version
  - mvn org.codehaus.mojo:versions-maven-plugin:2.7:set -DremoveSnapshot=true
  - CURRENT_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
  - git add pom.xml **/pom.xml
  - git commit -am "Releasing Project $CURRENT_VERSION"
  - git tag -a $CURRENT_VERSION -m "Releasing Project $CURRENT_VERSION"
  - git push $CURRENT_VERSION

  #roll the next version
  - mvn org.codehaus.mojo:versions-maven-plugin:2.7:set -DnextSnapshot=true
  - CURRENT_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
  - git add pom.xml **/pom.xml
  - git commit -am "Bumping pom.xml version to $CURRENT_VERSION"
  - git push

When you push the tag it pushes the corresponding commit with it. When CI sees the tag, it starts the pipeline on the basis of a tag ref, since our publish stage only listens to tag references, we end up building the code as expected.

It has to be said, with so many individual maven plugins having to be configured to produce a released artefact and tagged commit, it might be worth sitting down and writing a better maven relase plugin, one that ideally orchestrates the whole flow using existing plugins.