Pre-pre-build commands with qmake
With most non-trivial Qt projects that I create, I like to include a pre-pre-build command in
the qmake
project file. I'll explain why as we go, but first
off, let's look at what I mean by "pre-pre-build" (it is not at all a standard term).
The typical build process (as performed by make looks something like this:
- For the given target, check if any of the target's dependencies have been updated since it was last (re)built.
- If no dependencies have changed, do nothing - we're done ;)
- If one or more dependencies have changed, then:
- Build each dependent target.
- Build this target.
- If appropriate, link this target with its dependencies.
That is, of course, a gross oversimplification... but it will do the purpose of this post.
Now, like most Qt developers, I use qmake to generate the
Makefiles that drive the build process. qmake, being
an excellent tool, allows you to customise the generated Makefiles in many, many ways. For example, you can add custom
commands to be executed just before or after the link step (1.ii.c above) via the
QMAKE_PRE_LINK
and
QMAKE_POST_LINK
variables.
However, qmake does not provide a QMAKE_PRE_BUILD
variable for adding pre-build commands, and certainly no
QMAKE_PRE_PRE_BUILD
variable either. The former, it turns out, is pretty easy to achieve anyway, but the latter (the
topic of this post) is a little bit trickier.
So, to explain what I see as a "pre-pre-build" step, let's repeat the steps show above, but with my imaginary pre-build and pre-pre-build steps included:
- Execute pre-pre-build commands.
- For the given target, check if any of the target's dependencies have been updated since it was last (re)built.
- If no dependencies have changed, do nothing - we're done ;)
- If one or more dependencies have changed, then:
- Build each dependent target.
- Execute pre-build commands.
- Build this target.
- If appropriate, link this target with its dependencies.
So you can see we now have two new steps: 1 and 2.ii.b, which are pre-pre-build and pre-build respectively.
As mentioned above, it's pretty easy to create pre-build steps - you can, for example, create a new build target, and
add it to the existing target's dependency list... but that's not the same as my pre-pre-build concept. The key
difference being this: pre-pre-build commands will always be executed at the start of the make
call, whereas
pre-build commands will only be executed if the target needs to be rebuilt (ie if at least one of the target's
dependencies has been updated).
So, why does that distinction matter? Well, to put it simply - it matters if/when the pre-pre-build commands might result in modification / updates to another target's dependencies! That being the case, clearly they need to be executed before target dependencies are evaluated.
Ok, so let's get to an example, which should make things a little bit clearer: Auto-updating Subversion build numbers. It's not uncommon (just search the Qt-interest mailing list) for people to want to automatically update part of a project's version number based on a revision control system's revision number. Some examples here, and here.
The idea is simple - if a project's version number automatically reflects the Subversion (in my case) revision number, then not only do I have an increasing and unique version number, but I can also easily look back a the repository's commit log to see exactly what changed between releases.
Now many people implement such auto-revision version numbers like this suggestion, which results in a pre-build step, rather than a pre-pre-build step. Which means this:
- When the target's dependencies have changed, and thus a (re)build will occur, both pre-build and pre-pre-build commands would result in the same behavior - the target is built, and receives the current build number automatically.
- But, when the target's dependencies have not changed, but the project's revision count has, then the pre-pre-build approach will result in the target being rebuilt (just to update the build number), whereas the pre-build approach will result in the target being left as is (ie with some earlier version number).
Of course, the "correct" behavior is mostly up to personal preference... on the one hand, if the target itself has not changed, then why update it's version number? but the on the other hand, if you have multiple qmake projects in a given application (eg libraries, etc) then you may (as I usually do) want them to all build to the same release version number.
And, of course, this is just one example... there are many other reasons that you may want a "pre-pre-build" command.
Ok, so that's it for the intro / justification... so how do we do it?! Well, since it's not a standard qmake feature, it is slightly "hackish", but it works by modifying the qmake project file as follows:
- Include a custom qmake target using
QMAKE_EXTRA_TARGETS
. - Hook that custom target into the very beginning of the build process (this is the tricky / hacky bit).
So, first off, we create a custom qmake target. I'll call the target svnbuild
, since it updates my project's build
numbers to match the Subversion revision count, but obviously you can call it something more appropriate for your
particular application.
# Create our custom svnbuild target.
win32:svnbuild.commands = ..\tools\build\updateBuildNumber.bat ..\svnbuild.h
else:svnbuild.commands = ../tools/build/updateBuildNumber.sh ../svnbuild.h
QMAKE_EXTRA_TARGETS += svnbuild
Here you'll see I've got external updateBuildNumber.bat/sh
scripts that do the Subversion checking etc - the *.bat
file for win32 builds, and the *.sh
script for all other (non-win32) platforms.
Now, the (slightly) tricky bit - hooking our custom svnbuild
target in as the first step in the generated Makefile
.
Here goes:
# Hook our svnbuild target in between qmake's Makefile update and the actual project target.
svnbuildhook.depends = svnbuild
CONFIG(debug,debug|release):svnbuildhook.target = Makefile.Debug
CONFIG(release,debug|release):svnbuildhook.target = Makefile.Release
QMAKE_EXTRA_TARGETS += svnbuildhook
So how does that work? Well, to understand, (and here comes the hacky bit) let's look at the Makefiles that qmake
generates... assuming that we have not added the above hook yet, the first build target for the generated
Makefile.Debug
and Makefile.Release
files is all
(no surprises there). Now here's the convenient part - the all
target actually builds Makefile.Debug
(or Makefile.Release
, depending on the build mode) before building the actual
project target. This is done (presumably) so that the generated Makefile.*
files are automatically updated if/when
the primary Makefile
file is updated. But since the Makefile.*
targets are always evaluated first (thanks to the
default generated all
rule), we can simply add a custom build hook target (svnbuildhook
) as the build rule for
the Makefile.*
targets... this works because we have not defined any commands for that hook target (ie we don't
set svnbuildhook.commands
), and so make
sees an entry like this:
Makefile.Debug: svnbuild
make
interprets this line as simply declaring an additional dependency for Makefile.Debug
(or Makefile.Release
),
since it does not define any build commands. That is to say, if we skipped the svnbuildhook
intermediate target, and
instead set our svnbuild
target directly as the build rule for Makefile.Debug
, then qmake
would generate (and thus
make
would see) something like the following:
Makefile.Debug:
..\tools\build\updateBuildNumber.bat ..\svnbuild.h
Which make
would treat as the build rule for Makefile.Debug
instead of simply being an additional dependency
declaration.
Also, note that we did not set any dependencies for the svnbuild
target - by leaving svnbuild
's dependency list
empty, make
treats svnbuild
as a phony target,
which is highly desirably for a pre-pre-build target (it ensures that the associated commands will be executed every
time, not just when some dependency has changed... see
phony targets for more information).
Okay, well that's it. I know my explanation is a bit convoluted, but as long as you know what qmake
is, and you know a
bit about the Makefile
format, then you should be able to copy and paste the code into your own qmake
project files,
inspect the resulting Makefiles, and then customise to suit your own desired pre-pre-build commands.
Just to recap, the code to add to qmake
project files looks like:
# Create our custom svnbuild target.
win32:svnbuild.commands = ..\tools\build\updateBuildNumber.bat ..\svnbuild.hxx
else:svnbuild.commands = ../tools/build/updateBuildNumber.sh ../svnbuild.hxx
QMAKE_EXTRA_TARGETS += svnbuild
# Hook our svnbuild target in between qmake's Makefile update and the actual project target.
svnbuildhook.depends = svnbuild
CONFIG(debug,debug|release):svnbuildhook.target = Makefile.Debug
CONFIG(release,debug|release):svnbuildhook.target = Makefile.Release
QMAKE_EXTRA_TARGETS += svnbuildhook
Good luck!! :)