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.