What is in a name? Usually a version number, actually.

Another fascinating topic for you – build versioning! Ok, fun it might not be, but it is important and mostly unavoidable. In an earlier blog I outlined a build versioning strategy I was proposing to use with our Java builds. Since then, the requirements have changed, as they tend to, and so I’ve had to change the versioning convention.

Essentially, what I’m after is a way of using artifact version numbers to tell me some useful at-a-glance information about the artifact I have created. Also, customers want the version number to meet their expectations – that is, when they get a new build, they want to see an easily identifiable difference in the version number between the new build and their old one. What they don’t want is a long complicated list of numbers which are hard to distinguish. For instance, it’s much easy to identify which of the following 2 versions is the latest:

  • 5.0.1
  • 5.0.4

but it’s not so easy to work out which of these is the latest:

  • 5.0.1.13573
  • 5.0.1.13753

As we’re practicing continuous delivery, any given check-in can feasibly produce a release build. So, I would like some way of identifying exactly which check in produced my builds, or at least have a way of working out which bits of source code went into my released package. There are a couple of ways we can do this:

Tag the source code – We could make the builds tag the source code in our SCM system (Perforce) with every build. This is relatively easy to do using Ant and Maven. With Ant there are numerous different ways of doing it depending on your SCM system, for instance, with subversion you need to use the SvnAnt tasks from subclipse (http://subclipse.tigris.org/svnant/svn.html) and basically perform a copy of your source url:

 <copy srcUrl=”${src.url}” destUrl=”${dest.url}” message=”${version.num}”/>

(this is because tags in svn are just cheap copies with a label).

With Maven you just need to use the release plugin – this automatically handles tagging for you.

Tagging the source code is great – it keeps the version numbers as simple as I’d like, and it’s nicely traceable. However, it’s time consuming, and can result in a lot of tags.  The other problem is, I can’t tell which check-in caused the build just by looking at the version number of an artifact.

Use the commit number in the build version – We use a build version of Major.Release.Patch-Build in our artifacts. The build number used to be an auto-incrementing number – this worked fine but it didn’t give us a link back to which commit had caused the build to be made. So, I decided to use the perforce changelist id (i.e. the commit version) as the build number in the version, so that builds would end up looking something like this: 1.0.0-11531.

The problem here is that the version number is not customer friendly – so I remove the build number as a final step, before the builds get released to customers. To track what version the customers have got, I still keep a record of the full build number (including the commit number) in the release notes, and I could also easily inject it into an assembly info or properties/config file if I so wished, so that customers could very easily read out the full version number just by looking in a menu somewhere.

There were several obstacles I had to overcome to get this working. The first obstacle, and really this was the one that stopped me from tagging the source code, was that the maven release plugin is abysmal when it comes to continuous delivery. I needed to use the release plugin to tag the source code, but one of the other things that the maven release plugin does is to remove the word SNAPSHOT, increment the version number, and check the pom back into source control. This would cause another build to trigger in the CI system, which in turn would increment the build number etc and cause another build to trigger – so on and so on. Basically it would create a continually building project.

So I have decided not to use the maven release plugin at all – it doesn’t seem to fit in with Continuous Delivery. In order to create potential release candidates with every successful build, I’ve removed the word SNAPSHOT from all the poms, so we aren’t making any snapshot builds anymore either (except when you build locally – more on that later). The version in the poms now takes the P4 commit number, which is injected via the Continuous Integration system, which in my case is Go. Jenkins also supports this, using the subversion plugin (if you use subversion), which sets an environment variable with the svn revision number (more details here). The Jenkins Perforce plugin does the same thing, setting the P4_CHANGELIST environment variable – so it can easily be consumed (more details here).

Go takes the P4 changelist number and puts it in an environment variable called “GO_PIPELINE_LABEL”. I read this variable in, and assign it to a property called p4.revision. I do this in the command that kicks off the build, so that it overwrites a default value which I can keep in my pom – this is useful because it means my colleagues and I don’t have to make any changes to the pom if we want to run a build locally (bear in mind if we run it locally this environment variable won’t exist on our PCs, so the build would otherwise fail). Here’s a basic run down of a sample pom, with more details to follow:

<modelVersion>4.0.0</modelVersion>
<groupId>etc.so.forth</groupId>
<artifactId>MyArtifact</artifactId>
<packaging>jar</packaging>
<version>${main.version}-${build.number}</version>

<description>Description about this application</description>

<properties>
<p4.revision>SNAP</p4.revision>
<build.number>${p4.revision}</build.number>
<main.version>5.0.2</main.version>
</properties>

<scm>

</scm>

<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>release-candidate-repo</id>
<url>http://artifactory.me.com/my-rc</url&gt;
</repository>
</repositories>

<build>

</build>
</project>

The value for p4.revision is “SNAP” by default, meaning that if I make a local build, I’ll get an artifact with the version 5.0.2-SNAP. I know that these builds should never be promoted to production or handed to customers because the word SNAP gives it away.  However, when a build is created by the CI system, the following command is passed:

clean deploy sonar:sonar -Dp4.revision=${env.GO_PIPELINE_LABEL}

This overwrites the value for p4.revision, passing in the Perforce commit number, and the build will create something like 5.0.2-1234 (where 1234 is my imaginary p4 commit number).

I’ve added a property called main.version, which is the same as the full version but without the build number. I’ve done this so that I can package up my customer builds (ina  zip) and label them with the version 5.0.2. After all, customers don’t care about the build number.

An important policy to follow is once a build is released to a customer, one of the other version numbers MUST be increased, meaning all further builds will be at least 5.0.3. The decision of which version number to increase depends on various business factors – I like to increase the 3rd number if I’m releasing a patch to a previously released build. If I’m releasing new functionality I increase the second number. The first number gets increased for major releases. The whole issue of version numbers becomes a lot less complicated if you’re in the business of releasing software to web servers and you don’t actually have to hand software over to customers. In this instance, I just keep the full version number with the build number at the end, as it’s usually someone like me who has to look after the production system anyway!

15 comments

  1. Tyler · August 22, 2011

    Let’s say you ship 1.2.3-1000 to a customer but then discover it has a bug, so you follow up by shipping them 1.2.3-1001. If you strip off the build number, your customer sees version 1.2.3 is replaced by the bugfix version which is also 1.2.3. Isn’t this more confusing than just leaving the build number in the version string?

    • Tyler · August 22, 2011

      Heh, I must have skimmed over the answer to this question in the final paragraph!

      It does mean that you’ve introduced a weird dependency in your system: when a build is shipped to a customer, someone must remember to increment the last digit in the version triad.

  2. Anonymous · December 1, 2011

    Well this is a nice approach -I started wondering why isn’t everyone doing it this way….until I realized that this only works for simple one module projects. In multi-module projects it’s not possible to use a property for the parent triple (groupid:artifactid:versionid) -though some ancient maven versions might have allowed that but it’s a bug.

    • Anonymous · January 10, 2012

      I just tried using James’ scheme for versioning with one of my multimodule projects and it worked successfully. I simply replaced the parent pom version with the expression [${main.version}-${build.number}] and the build ran successfully and artifacts were deployed to Nexus Maven repository.

      I am using Maven 3.0.2

      What version were you using that failed?

      • Mike · January 20, 2012

        This approach does work well on my multi-module project, and I’d really like to use it. However, one problem I’ve encountered seems tough handle. If say I want to run an out-of-band plugin on a child module, I have to run it from the root module. For example, the version variables will not be evaluated if I did…
        cd child-module; mvn dependency:list

        However the version variables do evaluate correctly from the root module, you did…
        mvn dependency:list -pl :child-module

        This is fine for some, but will make adoption challenging for my peers. Any suggested work-around would be welcome.

      • Anonymous · January 21, 2012

        (I’m the one who told it would not work for multi-module projects)

        -Oh yes, might work if you can afford to always build from the top level. But usually devs want to build the sub-modules individually as well. If you build a sub-module and have that parent version as a property-maven can’t know which parent version it should pick…
        -the issue has been debated a lot: http://jira.codehaus.org/browse/MNG-624 and in all the duplicates&related issues there.

  3. RC · November 7, 2012

    “but one of the other things that the maven release plugin does is to remove the word SNAPSHOT, increment the version number, and check the pom back into source control. This would cause another build to trigger in the CI system…” Eeek… BROKEN CI SYSTEM ALERT.

    Your problem is staring you in the face, and yet your workaround is quite convoluted. You simply have run your Maven Releases as a user who is excluded from the commit trigger that starts your CI builds. Problem solved.

    I do all of this with TeamCity – the code is checked out by a “buildsystem” user who has access to our source (in Subversion). The Maven Release plugin is run in batch mode from a TeamCity build config (which can optionally take additional parameters); this is also run by the same “buildsystem” user. Our CI build configs are triggered by code commits, excluding… yes you guessed it “buildsystem”.

    Problem solved.

    • jamesbetteley · November 7, 2012

      Sounds like a nice solution! I’ve personally not tried to configure Jenkins, Bamboo or Go to not trigger a build if a change was committed by a particular user – I’ve had a quick look for this functionality in Jenkins but I haven’t found anything. Perhaps there’s a plugin for that (any info would be greatly appreciated). It sounds like a neat feature though!
      Am I correct in assuming that in your system every build creates a release build (a la Continuous Delivery)? I’ve gone into more detail as to why I don’t use the release plugin for continuous delivery in another post: https://jamesbetteley.wordpress.com/2012/02/21/continuous-delivery-using-maven/ perhaps there’s some more info there which might explain why I’ve stayed away from that particular plugin in general 🙂

      • RC · November 7, 2012

        Again, just my two cents, but the process I’ve been using since 2005 is:

        1. Build snapshots continuously and get them to the users/systems that need to see them (and accept the caveat that they could be unstable). Snapshots do not leave the delivery team (Dev/QA).

        2. Once snapshots have been verified safe for release, or your dev sprint has finished, or your customer demands a sneak preview… run the maven-release-plugin to create a release.

  4. RC · November 7, 2012

    The maven-release-plugin is designed for proper releases, not continuous integration, so it’s a little harsh to criticise it for not doing a job it was never designed for. Maven snapshots (as in the versions with the SNAPSHOT token in the version) are your continuously built artifacts and you perform a Maven release (using the plugin) when you have something stable enough to ship outside the delivery (Dev/QA) team.

    The reason I’m using TeamCity is precisely because of it’s ability to filter out commits by user, comment, file pattern and so on. The last thing you want in CI is unwanted builds!

    • jamesbetteley · November 7, 2012

      Thanks for your comments Richard. I particularly like your comment “The maven-release-plugin is designed for proper releases, not continuous integration”. That certainly seems to have been the prevailing mindset when they designed it! However, as I mentioned in the post, we’re practicing Continuous Delivery, meaning that for us, every build should produce a potentially deployable/releasable artifact. This flies in the face of the concept of SNAPSHOT and release builds being two different things. For us, every build must be a release build or it doesn’t fit into Continuous Delivery.
      One of the reasons why I produced the other post was because Maven is hugely popular, and although it was clearly not designed with Continuous Delivery in mind, I wanted to see whether it was actually possible to implement a successful Continuous Delivery model using it (as for many companies, changing your build tool is not an option).

  5. Usually I do not learn post on blogs, but I would like to say that this write-up very compelled me to check out and do it!
    Your writing taste has been surprised me. Thanks, quite nice article.

  6. Pedro · May 1, 2013

    Hi there, just wanted to mention, I liked this blog
    post. It was inspiring. Keep on posting!

Leave a reply to RC Cancel reply