Embedding Version Info

In software development, it's crucial to keep track of software configuration. We depend on version control tools for it. But how do we track versions of compiled binaries? In this post, you will find a simple solution for this issue.

There are a plenty of version control tools, out there. But keeping track of your code's version is not enough. Imagine, you have developed some application, and published the first version, say, v0.1. Many people downloaded it, and some later, you released v0.2. Maybe a year later, you released v1.0. At this point, you probably have a dozen different versions of your application downloaded by many different users. And you cannot ensure that everybody uses the latest version. In fact, as software users, most of us prefer slightly old -but stable- releases to fresh -but probably buggy- releases.

So, when users state something (a bug, or a feature request, or even a praise) about your application, you need to know which version they are talking about. Moreover, they need to know the version, so they can submit it with proper version information. After this, you can check it out from your code repository, and see what's wrong (or good, where it depends).

It's about software configuration management, and it's a long story which won't fit into my short articles. I will create a tag for the topic, and mark related posts with it, for the curious.

Here, I will introduce a simple method (which you can use with your Makefile) and explain how can you use it with three different (git, mercurial, and subversion) version control tools.

This method involves three elements: Some code (main.cpp), a Makefile, and a script (version.sh / version.py, whatever).

The method works like this: Whenever you build your code, a version header file is generated by version script first, and compile continues using this version file. So, version info from your version control tool, embedded into your compiled binary.

This is our simple main.cpp program:

#include <iostream>

#include "Version.h"

int main() {

    std::cout << "Version : " << VERSION_STR << std::endl;

    return 0;
}

And to build it, we will use following Makefile.

all: version
    g++ -o main main.cpp

version:
    sh version.sh

clean:
    rm *.o main Version.h

In GNU makefiles, you should use TAB characters for indentation, or else, you get following error:

Makefile:<N>: *** missing separator.  Stop.

<N> here denotes the line number of the faulty line.

After these two files, we need only our version.sh script. Contents of our script will differ for each version control tool, so start with this:

#!/bin/bash

FILE=Version.h

VSTR=v0.1

echo "#ifndef VERSION_H_" > $FILE
echo "#define VERSION_H_" >> $FILE
echo "#define VERSION_STR \"$VSTR\"" >> $FILE
echo "#endif // VERSION_H_" >> $FILE
echo >> $FILE

You may also check these files here on GitHub Gist.

You can now build the program, and run it to see -our dummy- version information:

$ make
sh version.sh
g++ -o main main.cpp
$ ./main
Version : v0.1
$ _

For the remaining steps, you should have these files under version control of your preference. You may simply git init, or hg init in the folder, or use svnadmin create to set up some testing repository. After that, all we need to do is replace the following line from version.sh script with some version control tool command. Details follow.

Git

For git, our command is git describe.

$ git describe --always --dirty
153a04f

We use --always flag to get a commit id, as a fallback. Without, our response would be,

$ git describe --dirty
fatal: No names found, cannot describe anything.

And we should use --dirty flag to reflect any uncommitted changes to version string. If this is the case, our output will be,

$ git describe --always --dirty
153a04f-dirty

If you wish, you may replace that "-dirty" keyword with anything you want by providing a parameter:

$ git describe --always --dirty=-uncommitted

Now, all we need to do is, update our version.sh file; just rewrite VSTR like this:

...
VSTR=$(git describe --always --dirty)
...

If we build and run our application again, we'll see that

$ ./main
Version : 153a04f-dirty

If we commit all our changes in and give a tag;

$ git tag -a v0.1.0 -m "Initial tag."

We'll see it after building again:

$ ./main
Version : v0.1.0

Play with it to see possible combinations. Git ends here.

Mercurial

For mercurial, we will use hg identify command.

$ hg identify
53fb20d5db35 tip

This command displays a commit id, and a label (or tag). If your workspace has some uncommitted changes, then the output will be like this:

$ hg identify
53fb20d5db35+ tip

So, following updates will apply to our version script, for a fancy output:

...
COMMIT_ID=$(hg identify | cut -d' ' -f1)
LABEL=$(hg identify | cut -d' ' -f2)

DIRTY=""
echo $COMMIT_ID | grep "+" 1>/dev/null 2>/dev/null
if [[ $? -eq 0 ]] ; then
    DIRTY="-modified"
    COMMIT_ID=${COMMIT_ID%?}
fi

if [[ $LABEL == "tip" ]] ; then
    VSTR=$COMMIT_ID$DIRTY
else
    VSTR=$LABEL$DIRTY
fi
...

When we run our application with this script, the output will be:

$ ./main
Version : 3a96920faf26-modified

or it would be:

$ ./main
Version : v0.1.0

Subversion

Even we call it old-school, subversion still widely used for version control. Here, two methods -a simple one, and a fancy one- will be introduced to get version information.

The simple one is:

...
VSTR=$(svnversion -n)
...

This will just include revision number based version information. If your code folder is controlled by subversion, then your application output will be:

$ ./main
Version: 1M

You may check the other output options by

$ svnversion -h

And for the fancy one, you should follow traditional subversion directory structure, that is trunk, branches, and tags tree. If you use this structure, and stick with svn switch command to switch between trunk, tags, and/or branches folders, then you may use following script:

...
REVISION=$(svnversion -n)
URL=$(svn info | grep Relative)

VSTR=$REVISION

echo $URL | grep trunk 1>/dev/null 2>/dev/null
if [[ $? -ne 0 ]] ; then
    LABEL=$(echo $URL | cut -d' ' -f3 | xargs basename)
    VSTR=$VSTR" [$LABEL]"
fi
...

When using this script, your output would be like:

$ ./main
Version : 3M [v0.1.0]

Conclusion

You may write your own scripts for other version control systems, or adapt this approach to other OS / platforms. As long as you generate a Version.h file, you may use other scripting languages, such as Python, or even C programs to get version information. In the end, what really matters is to get version information somehow embedded into your application.