Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

The BuildNumber Tool

4.00/5 (8 votes)
27 Apr 200616 min read 1   533  
In this article, I will present a few ways of numbering software releases, propose a scheme that is flexible enough to be used by the large majority of software projects, and introduce a tool that will help with the bookkeeping when trying to keep consistency in numbering releases.

Introduction

Software evolves. As much as we, both as programmers and as users, would like to have software that has all features we'd ever want and is completely bug free, and both these things at the time that we first buy the software, that is simply not going to happen. The requirements of users will evolve over the lifespan of any piece of software, and new versions with new or updated functionality and fixes for bugs will appear (hopefully) regularly. To be able to distinguish between these versions, software releases have been numbered since the early days of software development. How exactly this numbering is to be done depends on the complexity of the software and its components, the expected lifespan, and (sometimes) marketing considerations. In this article, I will present a few ways of numbering software releases, propose a scheme that is flexible enough to be used by the large majority of software projects (ranging from utility-written-in-an-afternoon-by-a-single-programmer to enterprise-class-multi-million-dollar-beast-to-be-used-for-the-next-50-years), and introduce a tool that will help with the bookkeeping that is unavoidable when trying to keep consistency in numbering releases.

Software numbering schemes

All sorts of numbering schemes can be thought up. To illustrate some of the considerations that come up when deciding on a scheme, I'll list a few here, and go over some of the advantages and disadvantages of each one. An important note to make here is that I will discuss naming schemes from the point of view of the software developer: the person or persons actually developing and maintaining the software. These people will have more detailed needs than the user of the said software: a developer needs to know exactly what version a certain user was using when, for example, a bug report comes in, so that he knows where to start fixing the bug or check if a certain bug has already been fixed in recent releases. A user is usually less concerned with the exact version of the software; he will only care when upgrading, to decide if newer versions have compelling features or fixes for bugs he experiences himself.

Single number

The simplest way thinkable (not considering numbering at all) is to give every release a number that is one higher than the last release that was made: release 1, 2, 3 etc. It's easy and fast but doesn't give much information other than that a certain version is newer than another one. It doesn't say anything about how different in functionality the versions are: versions 20 to 28 can be mere changes in wordings in dialogs and translations, while version 31 can differ from 30 in that a massive overhaul of the entire UI had taken place so that all users will need to be retrained to be able to use the software. Furthermore, the release numbers can quickly get very high: after developing for a couple of years with regular releases every couple of weeks, the numbers can go up into the hundreds, and it can go even faster when making daily builds or continuous integration runs.

Naming releases

Another way is to give certain releases names: either descriptive names ('CoolApp for multiple users', 'CoolApp with fast math module') or cutesy made-up names ('CoolApp the Bugs Bunny release' or 'CoolApp: the Supadupa edition'). It may be obvious that this naming scheme will appease the marketing department but is nearly unusable for the developer. When comparing two versions, nothing indicates which one is the most recent (one of the first things that is of interest), and for every release, a new name has to be decided on. This may seem like a not very practical way of naming releases but it is more often used than you would expect: often when a small team or one-man shop starts growing, and releases are made custom-tailored to certain customers, it seems to sort of grow that releases are described as 'that one time halfway 2004 that we send that version with the improved Foo accounting module to customer Johnson'.

Release-by-date

When there is only a single codebase that contains all the components that the software is built from, a date-based release scheme can be used. It's also simple: every release is named after the date it was released on. There are a couple of compelling advantages: it is immediately obvious which one of the two releases is the newest and how much newer it is, plus it is easy (when using a version control system or VCS such as Subversion or Visual SourceSafe) to rebuild the exact configuration by requesting the version of the source code from the VCS. However, there is also a disadvantage: when the development happens in spurts (a couple of months of heavy development followed by a year of doing nothing, and then another couple of weeks of moderate development), the dates say nothing about how different two versions are. The version at the end of a period of heavy development and at the beginning of the next period of heavy development can be months or years apart, while the functionality is almost the same; while the versions at the beginning and end of such a period can be very different yet only weeks apart.

Multi-part numbers

The last schema is the multi-part-number schema: it is composed of multiple numbers, getting the least significant going from left to right, all being incremented in steps of 1, and reset to 0 when a number on the left side is increased. There can be as many numbers as needed to granularize as much as needed, but the most I've seen in real-life use is 4. An example would be a version number '1.3'. This one has two numbers: the left one indicating the 'major' releases, and the right one indicating the 'minor' releases. In this example, there would have been one major release and three minor releases. When a new major release would be made (the number on the left would be increased), the 3 would be reset to 0: 2.0.

What constitutes as 'major' or as 'minor' releases is a matter that depends on the programmer, project manager, and/or marketing team. Small improvements would be 'minor' releases, major redesigns/new features would be a 'major' release. Now, to further specify how significant the changes in a certain release are, a third number could be introduced, so that the version number would be something like '2.4.1'. The third number would usually indicate bugfix releases: when a release only has bug fixes, the last number is increased; when there are minor new features, the second number is increased; when there are major new features, the first number is increased.

Sometimes, 3 'levels' aren't enough so a third one can be introduced. This fourth number is often used to number a daily build, and is therefore called 'buildnumber' (yeah, nobody has ever accused us programmers of being too inventive when it comes to naming things). So, a full version number could be '2.0.3.120', meaning 'major release 2, no minor releases, third bug fix release, 120th build'. Since the build number is usually only really useful to programmers, it's often not included in places where the version of the software is visible to the user (such as in the 'About' dialog and the documentation).

It's often not easy to keep the numbering consistent. One often used way to keep track of releases of a software team is to keep one Excel sheet on a fileserver that has a list of all releases, with their version number, release date, and some notes about what is actually in the release, and if you're lucky, a tag or revision number of the VCS. But there's a lot that can go wrong with this approach, because there is a lot of manual labor involved with it: before every release, the version number has to be changed in the code (like for the 'About' dialog), in the documentation, in the installer scripts, on the website, on the scripts in your website that respond to 'automatic version check' functionality in the software, and so on. Forgetting just one place can confuse users to no end, and can cause a lot of maintenance problems when users tell you 'I'm using 1.2.5' but they're really using 1.2.4, which didn't have that fix for that particular bug yet - while 1.2.5 did.

The obvious solution is to keep the version number in one file and have all other parts of your release process read from that file so that you only have to keep track of one single file, which can also be versioned by your VCS. That way you know which 'state' of your source code database belongs to which version. The rest of this article will present a tool to work with such a versioning file, and how you can use it to insert the version number of your software into the places where you need it.

Buildtool, its numbering scheme, and its file format

The tool is called 'BuildNumber' (see comments above about none too inventive programmers), and it works on version numbers that are composed of four parts:

  • a major release number
  • a minor release number
  • a patch level
  • a build number

All these numbers are stored in a file that contains a bit of XML in which every part is stored. The contents of an example of such a file is shown below:

XML
<version>
  <major_version>1</major_version>
  <minor_version>2</minor_version>
  <patchlevel>0</patchlevel>
  <build>123</build>
</version>

As you can see, very straightforward. It's easy for humans to understand and edit, but a file with a simple content '1.2.0.123' would be too. Why then bring the XML into it? Because, often you want to query and manipulate the file not by hand, but from scripts or scripted environments such as Visual Studio's pre- and post-build steps. By putting the information in a machine-readable format, it becomes easy for the BuildNumber tool to display parts or all of the version number and increase or reset parts of the version number.

Commandline options

The executable of the tool is called 'BuildNumber.exe'. It's a command-line tool for easy of integration into a scripted environment. I will first go over all the options that it understands, and then weave them all together in a couple of real-life usage scenarios.

The first thing to do is create a version file. That can be done manually, or by the BuildNumber itself, which has the '-c' command-line parameter for that. Typing 'Buildnumber.exe -c' will create a file called 'version.xml' in the current directory. Every part of the version number will be set to 0. We can also specify another filename to be used. That other name can be specified with the '-f' option. So, type in 'BuilderNumber.exe -c -f version.inc', and a file called 'version.inc' will be created with the same contents as the previous example. The -f option can be used not only when creating files, but also when querying them - which is what we'll do next.

To check what contents the new file has, we can of course open it with a text editor, but we can also use the '-d' ('display') option to show the whole number or parts of it. Type 'BuildNumber.exe -d' or 'BuildNumber.exe -d -f version.inc' to use another file than the version.xml file. (When no filename is specified, BuildNumber will look for a file called 'version.xml' and print an error message when no such file is found). To only display certain parts of the build number, a number of options can be passed to the -d option:

  • a: show only the major version.
  • i: show only the minor version.
  • p: show only the patch level.
  • b: show only the build number.
  • f: show the full version number, in this case '0.0.0.0'.
  • s: show the short version number, in this case '0.0.0'.

The short version is usually the one to display to users, so the 'full' version is meant to be used in internal builds, while the 'short' version is for actual releases.

To change one of the parts of the number, two additional options are available: '-i' and '-r' which stand for 'increase' and 'reset', respectively. Both these options accept one of four legal parameters:

  • a: work on the major version
  • i: work on the minor version
  • p: work on the patch level
  • b: work on the build number

The '-i' option will add 1 to the specified part of the number, while '-r' will reset it to 0.

In environments with a sane shell like cmd.exe (never thought I'd call cmd.exe a 'sane' shell, but in this context, it's OK) or bash, it's easy to redirect the output of the BuildNumber.exe tool to a file using the '>' operator. But for convenience, when working in environments where the '>' cannot be used, there is the '-o' parameter which you can use to specify a file to write the output to. So, by using BuildNumber like 'BuildNumber.exe -d s -o versionnr.inc', you'll write the short version of the version number to a file called 'versionnr.inc'.

When using the '-o' parameter and when a file with the same name as the name specified after the '-o' option already exists, BuildNumber will check the contents of the existing file and compare it to what it would write if the file hadn't existed yet. If they are the same, the file will not be written to again. This is to prevent the timestamp from being updated, since some environments (like Visual Studio) use that timestamp to determine whether to rebuild or not. By not updating the file, unnecessary rebuilds are prevented. If you do want to overwrite no matter what, there is the '--force' option (no short equivalent).

There are two last parameters that haven't been discussed yet, '-a' and '-p', but they will be discussed in the part on 'real-life usage examples', below. All these parameters (and their long equivalents) can be shown by specifying the '-h' option. It will show all available options, some examples, and short instructions on how to use BuildNumber.

Real-life usage examples

Now, this is all well and good, but how do you get the version number from the file into your program? The mechanism to do this is different for every programming language that you want to use BuildNumber with. The examples below will work on C/C++ programs, since that is the language BuildNumber was originally written for, but it's easy to extend it to support other languages. I'll add other languages in the next versions; if you want your favorite language to be supported by BuildNumber, see the next section.

In C/C++, the easiest way to work with a version number would be to have it available in a variable. That is where the '-p' (and the '-a') options come in. When using '-p', BuildNumber will surround the version number by some code that makes the output a legal variable declaration in C/C++, like this:

const char version[] = "1.0.0.0";

Now, by writing this declaration to a file, and then #includeing that file into our program, we have a variable available that can be used to set the version number in the title bar, 'About' boxes, startup splash screens, and wherever you can think of.

The '-p' option is used to indicate what programming language you're using. At the moment, there is only one parameter: 'c' to indicate the C/C++ programming language; that will produce an output such as the one above.

The '-a' option is used to supply a name for the variable. So, the following command:

BuildNumber.exe -d s -p c -a g_VersionNumber -o version.h

will produce:

const char[] g_VersionNumber = "1.0.0";

and write it to a file called 'version.h'.

The next step is to add the command that will create the include file to the build steps so that it is made automatically every time a build is done. In Visual C++, a good place is in the pre-build step.

A second place the version number is often needed is in the installer of the software. Again, how to pass values to your installer depends on what installer technology you use. I'll use NSIS [1] as an example. What I do is, use a VBScript file that builds the installer for the software. That VBScript file captures the output of the command 'BuildNumber.exe -d s' and then build a command line for the NSIS installer like this:

commandline = "makensis /DVERSION=" + versionnr + " nsisfile.nsi"

This will pass the value that is in the variable 'versionnr' as a constant named 'VERSION' to the installer script 'nsisfile.nsi' which can then use that constant in dialogs and when making a filename for the software.

Because BuildNumber is a command-line tool, it's easy to make it part of a tool-chain to build any part of your software.

Building BuildNumber and adding new programming languages to BuildNumber

BuildNumber.exe uses two libraries which you'll need to know of when trying to build it yourself: Boost::ProgramOptions and pugxml [2]. The former you'll have to download yourself from [3], the latter is included with the source code. BuildNumber itself is written in standard C++ using only the C++ standard library, so it should compile on all platforms where Boost and pugxml work. I haven't tried it on any other platform than Windows though - I'd appreciate feedback on how well (if at all) it works on other platforms.

To add new programming languages to BuildNumber, you have two choices: either edit the code yourself, or give me some information on how your programming language could work with BuildNumber. All I need is how to declare a variable in your language.

Conclusion

I've described a couple of ways to number software releases, along with some considerations on every method. Next, I've described a way that I think is enough for almost all software projects, and explained how to use the BuildNumber tool to manipulate version files. Finally, I've shown some examples on how to apply it in a real-world situation.

I hope this small utility will help you in maintaining a saner build environment, and will reduce the stress that goes with doing the release management of your software!

License

This code is released under the GPL [4]. That means that you can use and modify the code for free; when you make a modified version public, you'll have to release the source code to that modified version as well, or if you use this code in another way in your own code, you'll have to release the source code as well. In case you have any questions, contact me for clarification (or better yet, ask a lawyer in your own jurisdiction to explain it to you and answer any possible questions).

To be clear: you can use this software to build software that you don't want to release the source code for.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here