A project I’ve been working on uses an SVN repository for its version control. I needed a way to automatically insert the SVN revision into the code at compile time so that an “about” window could be automatically updated to display the current revision number. I’d like to share how I did it, both for my own future reference and for anyone else out there.

I got this working on a Linux build host, but I’m not sure exactly how well it will work on Windows. If you have Windows versions of sh, svnversion, and sed, it might be possible. I would assume it should work on Mac OS X just fine too. Let me know how it goes for you below.

Start out with a shell script

Let’s make a shell script that will grab the revision using the svnversion command. This script will go somewhere in the project directory. In my example, it’s going to go in a directory called “scripts” inside the main project directory. The script will be called updateSVNVersion.sh. You should make it executable with “chmod +x”.

#!/bin/sh
SVNVERSIONSTR=$(svnversion "$1" | sed -e "s/.*://")
VERSIONFILE="$2/version.cpp"

echo "/* This file is auto-generated by the build script. Do not modify it. */" > "$VERSIONFILE"
echo "#include \"version.h\"" >> "$VERSIONFILE"
echo "QString SVNVersionString() {" >> "$VERSIONFILE"
echo "    return \"${SVNVERSIONSTR}\";" >> "$VERSIONFILE"
echo "}" >> "$VERSIONFILE"

This script takes two parameters. The first parameter is the main project directory to run the svnversion command on. The second parameter is the build directory, which is where all the object files are stored during compilation. This is also where we will generate a file called version.cpp. The build directory is passed as a parameter so that the script knows where to put the generated file.

The output of svnversion is passed to sed to grab the second half of the version string if there are two halves separated by a colon. This ensures we grab the newer revision number of a mixed-revision checkout (this happens if you commit a file and then don’t update the full checkout, for example). This generated version string may also contain other letters such as M if there are pending changes that haven’t yet been committed.

Create version.h in the project directory:

#ifndef VERSION_H
#define VERSION_H

#include <QString>

QString SVNVersionString();

#endif // VERSION_H

All this file does is provide a prototype for the function in the auto-generated version.cpp file. Any code that needs access to the SVN revision will #include “version.h” and call SVNVersionString() to get it.

Now that we have the script, we need to make it automatically run before each build.

Don’t use a Qt Creator pre-build step…

I know someone will suggest just adding a pre-build step in Qt Creator to run a shell script. That’s great, except Qt Creator’s pre-build step is a user setting that isn’t stored with the actual project file. Pre- and post-build steps are stored in a .pro.user file that is used by Qt Creator. qmake knows nothing about it. The project I am working on has multiple developers, and it would be a big pain to make sure that everyone had their .user file set up correctly. Instead…

…do it in qmake/make

Doing it this way will work better.

The thing that’s annoying about setting it up this way is you have to make sure you get the dependencies correct so that version.cpp is recompiled every time you re-run make. I ran into several problems trying to put the SVN revision directly into header files until I finally pieced together this solution which made it easier to force version.cpp to be recompiled every time. No fear: just follow these steps and everything should work fine.

For the rest of these instructions we will be operating on your project’s .pro file. Add the generated version.cpp file to your SOURCES variable:

$$OUT_PWD/version.cpp

Note that I prefixed version.cpp with $$OUT_PWD. OUT_PWD is a qmake variable that points to the build directory. As we’ll see in a bit, there is also a variable PWD that points to the directory containing the current file, which in this case is the .pro file. Anyway, this entry may stick out like a sore thumb next to the rest of the entries in your SOURCES variable, but you have to do it this way because version.cpp is not stored in the same directory as the rest of your source files.

Next, add version.h to your HEADERS variable:

version.h

That one’s not complicated!

Finally, add some rules (again, somewhere in your .pro file) that will cause extra items to be added to the final Makefile to force version.cpp to be auto-generated:

PRE_TARGETDEPS += version.cpp
QMAKE_EXTRA_TARGETS += svnrevision
svnrevision.target = version.cpp
svnrevision.commands = $$PWD/scripts/updateSVNVersion.sh $$PWD $$OUT_PWD
svnrevision.depends = FORCE
QMAKE_DISTCLEAN += $$svnrevision.target

I’m honestly not a huge qmake or make expert, but I’ll explain these to the best of my ability. PRE_TARGETDEPS ensures that version.cpp is added early in the dependencies list, although I’m not 100% sure whether it’s necessary. I left it in place because it seems to work. QMAKE_EXTRA_TARGETS adds another target internally named svnrevision, which we set up in the next three lines. These lines create a Makefile target called version.cpp which is generated with the updateSVNVersion.sh script. Notice how we pass the source and build directories to the script as expected. The dependencies for this target are set to FORCE to force the target to run its command every time. This is a common thing done in Makefiles, and Makefiles generated by qmake do indeed include the FORCE target so it appears to be safe to use.

Basically, it causes this extra target to appear in the generated Makefile:

version.cpp: FORCE
        /path/to/updateSVNVersion.sh /path/to/source_dir /path/to/build_dir

Don’t add these lines to your Makefile; that will happen automatically. This is just an example to show what the end result looks like.

Finally, version.cpp is added to QMAKE_DISTCLEAN so it’s deleted if you run make distclean. Like I said earlier, I’m not an expert at getting qmake or make to work correctly, so I might have added some unnecessary extras. The important part is that this combination works for me!

One last thing: you may get a warning that version.cpp is not present when you run qmake the first time. It’s a harmless warning that occurs because the file doesn’t exist until you run make. Just ignore it and everything will still work fine.