Various Programming Hints

Peter Seiderer for http://www.ciselant.de

Qt qmake And Subversion svnversion

Incorporate a global Subversion1 revision number as build/revision version in a Qt2 project.

Subversion has the ability to substitute keywords3 e.g. the keyword 'Revision' in a file will be expanded to the last known revision in which the file changed. If you want some GlobalRev as build/revision number in your project some extra work has to be done using the Subversion tool svnversion4.

The following describes four different solutions for a Qt qmake based project checked into a Subversion repository. The first three solutions are based on a generated header 'revision.h' included from the main file 'example.cpp'. The fourth is based on a define directive at compile time.

Include file approaches

All three solutions are based on the same simple example files. A simple include file version.h (with a given define for the version string):
   1 #ifndef version_h
   2 #define version_h
   3 #define VERSION "1.2.3"
   4 #endif
A C++ file example.cpp with the main entry point printing the string 'version: <version>-build-<revision>' to stdout:
   1 #include "version.h"
   2 #include "revision.h"
   3 #include <stdio.h>
   4 
   5 int main(int argc, char* argv []) {
   6     printf("version: %s-build-%s\n", VERSION, REVISION);
   7     return 0;
   8 }

Simple solution (update on every build)

The qmake project file example1.pro creates an up to date revision.h file with svnversion generated revision string on every build:
   1 PRE_TARGETDEPS += $$OUT_PWD/revision.h
   2 
   3 HEADERS = version.h
   4 SOURCES = example.cpp
   5 TARGET = example
   6 
   7 QMAKE_EXTRA_TARGETS += revtarget
   8 revtarget.target = $$OUT_PWD/revision.h
   9 revtarget.commands = @echo \"updating file $$revtarget.target\"; \
  10   echo -e \"/* generated file (do not edit) */\\n\" \
  11     \"$$LITERAL_HASH ifndef revision_h\\n\" \
  12     \"$$LITERAL_HASH define revision_h 1\\n\" \
  13     \"$$LITERAL_HASH define REVISION \\\"`svnversion -n $$PWD`\\\"\\n\" \
  14     \"$$LITERAL_HASH endif\" > $$revtarget.target
  15 revtarget.depends = FORCE
  16 QMAKE_DISTCLEAN += $$revtarget.target
Example of a generated revision.h file:
   1 /* generated file (do not edit) */
   2 # ifndef revision_h
   3 # define revision_h 1
   4 # define REVISION "828"
   5 # endif
Example output:
version: 1.2.3-build-828
for a subversion working directory up to revision r828, or
1.2.3-build-828M
with local modifications.

The downside of this solution is the every time rebuild of the executable because of the generated file 'revision.h'.

Better solution (update only in case a relevant file changed)

The main thing changed is line 15, the dependency is changed from 'FORCE' to '$$SOURCES $$HEADERS $$FORMS $$PWD/example2.pro' (mind the '$$PWD' changes which are needed for an out of tree build). Now a new revision.h is generated each time one of the relevant source/header/forms/project file is changed, see example2.pro:
   1 PRE_TARGETDEPS += $$OUT_PWD/revision.h
   2 
   3 HEADERS = $$PWD/version.h
   4 SOURCES = $$PWD/example.cpp
   5 TARGET = example
   6 
   7 QMAKE_EXTRA_TARGETS += revtarget
   8 revtarget.target = $$OUT_PWD/revision.h
   9 revtarget.commands = @echo \"updating file $$revtarget.target\"; \
  10   echo -e \"/* generated file (do not edit) */\\n\" \
  11     \"$$LITERAL_HASH ifndef revision_h\\n\" \
  12     \"$$LITERAL_HASH define revision_h 1\\n\" \
  13     \"$$LITERAL_HASH define REVISION \\\"`svnversion -n $$PWD`\\\"\\n\" \
  14     \"$$LITERAL_HASH endif\" > $$revtarget.target
  15 revtarget.depends = $$SOURCES $$HEADERS $$FORMS $$PWD/example2.pro
  16 QMAKE_DISTCLEAN += $$revtarget.target

Perfect solution (update every time the svn status changes)

The qmake project file example3.pro:
   1 PRE_TARGETDEPS += $$OUT_PWD/revision.h
   2 
   3 HEADERS = version.h
   4 SOURCES = example.cpp
   5 TARGET = example
   6 
   7 LITERAL_DOLLAR = $
   8 QMAKE_EXTRA_TARGETS += revtarget
   9 revtarget.target = $$OUT_PWD/revision.h
  10 revtarget.commands = @SVNVERSION=\\\"`svnversion -n $$PWD`\\\"; \
  11   SVNFILEVERSION=`if [ -e $$revtarget.target ]; then \
  12       grep REVISION $$revtarget.target | \
  13       awk \'{ print $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}4 }\' ; \
  14     else echo unknown; fi;`; \
  15   if [ $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}SVNVERSION == \
  16     $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}SVNFILEVERSION ]; \
  17   then \
  18     echo \"svn status not changed - keep file $$revtarget.target\"; \
  19   else \
  20     echo \"svn status changed - updating file $$revtarget.target\"; \
  21     echo -e \"/* generated file (do not edit) */\\n\" \
  22       \"$$LITERAL_HASH ifndef revision_h\\n\" \
  23       \"$$LITERAL_HASH define revision_h 1\\n\" \
  24       \"$$LITERAL_HASH define REVISION \\\"`svnversion -n $$PWD`\\\"\\n\" \
  25       \"$$LITERAL_HASH endif\" > $$revtarget.target; \
  26   fi
  27 revtarget.depends = FORCE
  28 QMAKE_DISTCLEAN += $$revtarget.target
This is the only solution which forces a recompile in case of a svn status update without file update (e.g. 'svn update' or 'svn commit').

Define directive approach

This one uses a slightly different file example.cpp (version.h is recycled unchanged) with changes to print a given defined 'REVISION' string (or 'unknown' otherwise):
   1 #include "version.h"
   2 #include <stdio.h>
   3 
   4 #ifndef REVISION
   5 #define REVISION  "unknown"
   6 #endif
   7 
   8 int main(int argc, char* argv []) {
   9     printf("version: %s-build-%s\n", VERSION, REVISION);
  10     return 0;
  11 }

Simple define based solution

The define for 'REVISION' is set on compile time (see line 1) of the qmake project file example4.pro:
   1 DEFINES += "REVISION=\\\"`svnversion -n $$PWD`\\\""
   2 
   3 HEADERS = version.h
   4 SOURCES = example.cpp
   5 TARGET = example
   6 
This approach works only for very small projects (like the given example project) with only one source file. It will fail to update the revision string in case of a multi source file project if the main file does not depend on all other ones.

Footnotes

... Subversion1
http://subversion.apache.org
... Qt2
http://qt.nokia.com
... keywords3
http://svnbook.red-bean.com/en/1.7/svn.advanced.props.special.keywords.html
... svnversion4
http://svnbook.red-bean.com/en/1.7/svn.ref.svnversion.re.html


ciselant.de 2012-02-23