August 30, 2013

How we do versioning

Keeping track of version numbers as a part of the release management process seems to be one of these things that should be simple, but is done in so many different ways. Here is the approach we use at Headfitted that works very well for us:

First, let's take a look at semver. This is an initiative of keeping a common standard for version numbers that should be supported whenever possible. In short a version number should look like this:


This is what we use.
In both our node/Javascript projects and in our .NET projects, we keep the version number at one central place in the source code. The tooling and build scripts do the rest: Making sure this version number is stamped into all compiled binaries and static script files.

In node/Javscript we have a version.json file simply like this:

{ 'version' : '1.2.0' }

In .NET we need to do a few tricks to keep things simple. First, in all AssemblyInfo.cs files for our Visual Studio solution, we strip out all version number related stuff, and instead we create one SolutionInfo.cs file that looks like this:

[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyVersion("")]

We don't provide an AssemblyFileVersion, because by omitting this, it will take the same version as stated in AssemblyVersion. Why make it more complicated?

Finally we add a kind of symbolic link to SolutionInfo.cs from each of the projcets of our solution: Add -> Existing Item, and then click the arrow to be able to select Add As Link:

This way we only need to maintain the version number at one place for the entire Visual Studio Solution.

Another obstacle is that in .NET the AssemblyVersion is done like this: MAJOR.MINOR.BUILD.REVISION. So to make it easy we let Microsoft have it's will and let it name it "BUILD", when really it is Semver's "PATCH". Peace. We simply only use the first 3 parts, and always keep the REVISION at 0.

So when should the different parts of the version number be incremented? How and by whom? Semver gives us the answer to the first question:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.

How and by whom? This is where you need to make some decisions on the practical aspects. In our case we made these:
  • MAJOR and MINOR are maintained in the source code.
  • PATCH is maintained by the build server.
This works because we run all projects through a build server. In the code, we typically bump the MINOR (or even the MAJOR) at the beginning of a sprint/development cycle. Our commit comments look like this:

Bumped minor version. We are now working on version 1.2.

Now each time our build server makes a build, it will extract the MAJOR.MINOR from the source code, and call a very simple homemade counter service. This service will take any key and maintain a counter for this key. So when we pass the project name and MAJOR.MINOR to it (eg "projectX-1.2"), we might get "1" back, and "2", "3", ... on each subsequent call. This is our PATCH, and now the build server has the complete version number to stamp into the source code before building.

This works beautifully because if we change MAJOR or MINOR in the source code, the key will be new, and PATCH numbering will automatically restart at 0. It also makes sure that each build gets an unique version number, even if we have multiple build configurations (eg. test, staging, release). In our build server (Teamcity), we echo back the version number to the server, so we get a nice overview like this:

We don't check the final version number back into version control. In our source code, version numbers will always have PATCH set to 0. The build server replaces this with the correct PATCH number, builds, and then discards the changed source files. When building locally on a developer machine, PATCH will always be 0. This way we never have version number merge conflicts, and we avoid polluting the commit history with patch increments.

Our approach works well with our branching model, as the MAJOR and MINOR always goes with the source code (and branch). In the above screen dump, you can see how our Release Candidate and master branch is on version 2.16, while the develop branch has progressed and is on 2.17.

The result
We have unique version numbers, we follow common Semver conventions, the version number is always present in our releases, and it works well with our branching model.
Whenever possible, we discretely display the version number on the screen, typically in the footer. This is a great help for our issue tracking, so we can track exactly at what version a given bug started appearing, and we can say from which version the bug was fixed. We also use the version number for update detection, especially in single-page Javascript apps, so we can notify the user if a new version was deployed and the page should be refreshed. With some clever appending the version number to Javascript file names, we can also avoid browser-caching-old-version issues.

No comments:

Post a Comment