Index Software

Undocumented Qmake

Last Update: 2006-08-29

In July 2006, I changed jobs, and no longer use Qt at work. I intend to dabble a bit with Qt, but I won't be spending anything like as much time with it as previously. So I don't expect to be adding much to this page from now on.

If you're using Qt, then the chances are you use qmake. It's possible to integrate Qt into other make systems, but for most people, it's a lot of hard work for little gain. Personally, I find qmake quite good, but it has one big problem. The documentation is incomplete. In addition to the documentation available in assistant, I've found it invaluable to search through the qmake source code to find out how things are done, and also to discover undocumented features. This web page is intended to document some of these features. The current version of Qt at the time of writing was Qt 3.3.4.

My experience of Qt 4 is quite limited. However, I do have the impression that qmake has been substantially rewritten, and that the documentation is more exhaustive

The other thing that I've found invaluable is to study the qmake.conf files in $QTDIR/mkspecs attentively. From studying these files, you can see how qmake does things by default, and you can adapt them to you needs if required.

I've tested most of these things on Solaris, Linux and HP-UX. I just about got most things to work, with some difficulty, on Windows 2000 using cygwin.

Using $$ORIGIN

Most unices allow you to specify the RPATH for applications. This is part of the path that will be searched for dynamic libraries. In addition, you can specify a relative path with the $ORIGIN variable. This is not an environment variable, it is part of the application's RPATH. For instance, say the application is installed in /opt/foo/bin/fooapp and there are dynamic libraries in /opt/foo/lib. You could specify the absolute path (/opt/foo/lib) in the application. Or alternatively you could specify the relative path with $ORIGIN/../lib. Enough background. How do you actually do this? On Solaris with Sun Studio, it would be achieved by adding -R$ORIGIN/../lib to the link command. (g++ uses -Wl,-rpath, instead of -R). The qmake variable for this is, naturally, QMAKE_RPATH. So what is the problem? Well, the makefile needs to contain '-R$$ORIGIN/../lib' (two dollars so that it doesn't take $ORIGIN for a make MACRO).

If you are using Qt 3.3.8 (and I guess 4.x, but I haven't checked), then you can simply use \$$ORIGIN. But if you are using an older version of Qt (I've checked this with 3.1.1), then you have a problem. $$ORIGIN looks like a variable to qmake. I've tried all sorts of things to escape the dollars, ($$$$ORIGIN, \$\$ORIGIN, single quotes, double quotes ...). But always either the $$ORIGIN is treated as a variable and disappears, or else the escaping is too efficient, and the escape characters end up in the generated makefile. One solution is to set an ORIGIN environment variable:

export ORIGIN=\$\$ORIGIN

and then use that in the project file:

QMAKE_LFLAGS = -L/bar/build/lib -R$$(ORIGIN)/../lib -library=stlport4

Another possibility, a bit cleaner, is to beat the qmake variables at their own game:

DOLLAR = $
QMAKE_LFLAGS = -L/bar/build/lib -R$${DOLLAR}$${DOLLAR}ORIGIN/../lib -library=stlport4

The first curly braces aren't strictly necessary. Just imagine explaining that by telephone support, "minus r dollar dollar dollar dollar dollar dollar". Hmm!

QMAKE_EXTRA_WIN_TARGETS, QMAKE_EXTRA_WIN_COMPILERS

The documentation covers QMAKE_EXTRA_UNIX_TARGETS and QMAKE_EXTRA_UNIX_COMPILERS, but not their WIN counterparts. These certainly do exist for Windows as well.

No . in header include path

Add "no_include_pwd" to CONFIG and -I"." will no longer be added to INCPATH. You might want to do this, for instance, if you have a file like string.h in your project, but you want to include the system version by default.

No elimination of duplicate entries in LIBS

Add "no_smart_library_merge" to CONFIG.

Escaping '{' and '}' in a project file

From qt-interest mailing list

First, echo ${0##*/} is the correct thing to use.

Getting paths for MS SDK and DDK

From qt-interest mailing list

I think that this is also on an 'undocumented Qt' wiki.

# Find the SDK
SYSTEM_CMD = $$quote(reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftSDK\Directories /v "Install Dir")
SYSTEM_RET = $$system($${SYSTEM_CMD})
FORLOOP = 8 9 10 11 12 13 14 15 16
for(i, FORLOOP):SDKPATH += $$member(SYSTEM_RET, $${i})
SDKPATH = $$quote($${SDKPATH})

# Find the newest DDK containing headers/libs for $${DDK_TARGET}
DDK_TARGET = w2k
SYSTEM_CMD = $$quote(reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WINDDK)
SYSTEM_RET = $$system($${SYSTEM_CMD})
FORLOOP = 12 11 10 9 8 7 6 5
for(i, FORLOOP) {
  REG_CURENTRY = $$member(SYSTEM_RET, $${i})
  count(REG_CURENTRY, 1) {
   SYSTEM_CMD2 = $$quote(reg query $${REG_CURENTRY} /v "LFNDirectory")
   SYSTEM_RET2 = $$system($${SYSTEM_CMD2})
   FORLOOP2 = 7 8 9 10 11 12 13 14 15
   CURDDKPATH =
   for(j, FORLOOP2) {
    CURDDKPATH += $$member(SYSTEM_RET2, $${j})
   }
   CURDDKPATH = $$quote($${CURDDKPATH})
   exists($${CURDDKPATH}/inc/$${DDK_TARGET}):DDKPATH = $$quote($${CURDDKPATH})
  }
}

Setting system-dependent options

The existing qmake support is sufficient for most needs, in particular, the value of the QMAKESPEC environment variable. There are, however, a few cases where this might not be enough, e.g., if you need to distinguish between Linux on x86 and PPC, or between Solaris on SPARC and x86. As an example, on Unix systems (possibly including Windows with cygwin), then you could use

SYSTEM=$$system(uname -sp | sed 's/ /_/g')

An alternative [Qt 3.3 and later] is to use properties. For each system that you use, you would issue a command like

qmake -set SYSTEM LinuxPPC

You could then use in your project file something like

CONFIG += $$[SYSTEM]

LinuxPPC {
 message(Linux on PPC)
}

The one slight drawback of this approach is that it requires each user to perform the 'qmake -set' step to correctly configure their environment. Usually this would fall slightly out of the scope of a source management system. Thus you would not be able to do something like "checkout & make". If you do take this sort of approach, then you can protect yourself against errors like the property not being set like this:

isEmpty($$[FOO]) {
error(You must run qmake -set FOO whatever before running qmake)
}

Adding to INCPATH after $QTDIR/include

In your .pro file, add

QMAKE_INCDIR_QT += my/path

Not linking with the output of QMAKE_EXTRA_UNIX_COMPILERS

qmake assumes that the output of the extra compilers is destined to be linked into the final library or application. If this isn't the case, then you need to do something to stop it.

If "mycompiler" is the name of your compiler compound variable, then if you have a member .CONFIG set to no_link, then the output won't get linked. Example.

mycompiler.output = ${QMAKE_FILE_BASE}.out
mycompiler.input = MYCOMPILER_INPUT_FILES
mycompiler.commands = foo ${QMAKE_FILE_NAME}
mycompiler.CONFIG = no_link
QMAKE_EXTRA_UNIX_COMPILERS += my_compiler

Examples when you might want to do this are for things like generating documentation.

Using flex and bison

qmake has built in support for lex and yacc. Unfortunately, it won't work with flex and bison.

lex

lex is quite easy, just set

QMAKE_LEX = flex

bison

bison is another story. With a bit of effort, though, it can be coerced. The first step is easy, like lex.

QMAKE_YACC = bison

After that, we're in trouble. yacc generates files "y.tab.h" and "y.tab.c". Qmake adds to the makefile rule a command that moves these files to "<myfile>_yacc.h" and "<myfile>_yacc.c".

bison generates "<myfile>.Y.tab.c" and "<myfile>.Y.tab.h" so the move stage fails, and also evidently so does the compile.

So I used QMAKE_YACCFLAGS_MANGLE, which aren't documented. I think that it is supposed to allow you to change the way that variables are names (default prefix is "yy_", default qmake prefix is "<myfile>". I've kept this prefix, but forced bison to generate "y.tab.h" and "y.tab.c", which is what qmake needs for the subsequent rename and then compile. Phew!

My solution on Solaris didn't work on Linux. This time, I used the same bison option to control the output file name, but I also needed -d to generate the header file. I also had to clear QMAKE_YACC_HEADER/QMAKE_YACC_SOURCE.

On Solaris

Using the sfw version of bison, 1.35

solaris-cc {
QMAKE_YACCFLAGS_MANGLE = -o y.tab.c -p $base
}

On Linux

linux-g++ {
QMAKE_YACCFLAGS = -d -o y.tab.c
QMAKE_YACC_HEADER =
QMAKE_YACC_SOURCE =
}

Adding a doxygen target

From qt-interest mailing list

You can at least make "custom targets", such as for 'make strip'. See docs about qmake. Not sure how to run them automatically after linking though.
Example (for running doxygen over the sources with 'make doc'):

# custom target 'doc' in *.pro file
dox.target = doc
dox.commands = doxygen Doxyfile; \
    test -d doxydoc/html/images || mkdir doxydoc/html/images; \
    cp documentation/images/* doxydoc/html/images
dox.depends =

...
# somewhere else in the *.pro file
QMAKE_EXTRA_UNIX_TARGETS += dox

Stripping executables

From qt-interest mailing list

You can use something like this in the .pro file (untested):

unix:QMAKE_POST_LINK=strip $(TARGET)
!unix:QMAKE_POST_LINK=xxx $(TARGET)

Replace xxx with the same line but with whatever the strip command is called on Windows.

Building in subdirectories

Perhaps the simplest is something like

  system(cd contrib/ssh-askpass && $${MAKECMD})

Where you would have previously set the appropriate MAKECMD.

Pre-install headers

I had a lengthy battle at my place of work getting something like "make install_headers" to work (or even better, to be done as a pre-requisite before any other targets, so just typing make will pre-install any exported headers). Why? Well, we have a lot of source that includes headers in the common unix way as #include <library/header.h>, in particular when the headers are going to be exported. This poses no problem for a library that has all such headers in a directory "basedir/include/library", you can just add INCLUDEPATH += include, and all will be well.

When the files aren't organized in this way, there are three possibilities.

  1. Edit the files. Not an option for me.
  2. Change the directory structure (or fake it with links). This would work in most cases, but I have a few libraries that don't conform to the pattern of one source directory for one #include directory.
  3. Fight it out with qmake.

As you may have gathered, it's number three that I'm going to pursue here. I've had three goes at solving this problem, all with varying degrees of success. Sadly none of them seems to be 100% reliable on all platforms. I usually find that solutions 2 and 3 take a few iterations of qmake and make before everything settles down (i.e., the intermediate moc and ui generated files have been generated).

1. Use the INSTALLS qmake variable

This sounds like it fits the bill, but unfortunately, there are a couple of gotchas, which I'll come to shortly.

Here's how it works. You define a compound variable (I usually call it "headers". If you need more than one destination, you'll need more than one compound variable. Within your variable, you define the path (where the files will go) and the files (which files to copy).

Example. This assumes that you've already defined INSTALLBASE, TARGET, VERSION and HEADERS.

headers.path = $$INSTALLBASE/include/$${TARGET}_$${VERSION}/$${TARGET}
headers.files = $$HEADERS
INSTALLS += headers
PRE_TARGETDEPS += install_headers

Now for the gotchas. First of all, by default qmake will generate a makefile that uses the basic OS copy function to install the files (e.g., cp on Unix and copy on Windows - don't know for Mac). This will cause the destination file to have its timestamp set to the time that it was copied. Obviously, this is a calamity for an interdependent set of libraries built with make. The updated timestamps will cause a lot of unnecessary compiles. There is a solution. QMAKE_COPY to the rescue! Simply set this to something like "cp -fp" and all will be well.

On Windows, I had a bit of a harder time, and I ended up creating a little shell script to overcome the fact that though I was running nmake within cygwin, within nmake, paths all use backslashes, which cygwin copy doesn't understand. The script basically does

cp -fp `cygpath -u "$@"`

Now for the second gotcha. This one has no real solution. My aim was to have

HEADERS = ...
INSTALLS = ... as above ...
PRE_TARGETDEPS += install_headers

All is well until you want to add any uic or moc generated headers to INSTALLS. Then everything goes pearshaped. There's no problem putting the generated headers before install_headers in PRE_TARGETDEPS. There is a problem with a circular dependency though. qmake adds 'all' to the dependencies for INSTALLS, but the uic/moc generated headers depend on the .ui/.h files. Some versions of make will just complain, others will terminate when they see a circular dependency.

I think that the best solution would be to fix qmake to allow us to turn off the dependency on 'all'. For example

headers.path = $$INSTALLBASE/include/$${TARGET}_$${VERSION}/$${TARGET}
headers.files = $$HEADERS
headers.CONFIG += no_all
INSTALLS += headers
PRE_TARGETDEPS += install_headers

Note: you may want to have different variables for exported and private headers, e.g.,

PRIVATE_HEADERS = ...
EXPORTED_HEADERS = ...
HEADERS = $$PRIVATE_HEADERS $$EXPORTED_HEADERS
...
headers.files = $$EXPORTED_HEADERS

We'll have to wait and see if Trolltech take it up (or anyone else for that matter).

Because of this problem, I gave up with this solution wherever I need to install generated headers.

But, hold the presses!

1.a Use INSTALLS and patch qmake

I took up my own challenge and made a small (and as it turns out, quite simple) mod to qmake ($QTDIR/qmake/generators/makefile.cp). Here is the patch. Before I made this patch, I was using INSTALLS for projects where ui generated headers were not required to be public. I was using the method outlined in point 2 below for those libraries where I needed to export generated headers. Now I use INSTALLS for everything.

2. Use QMAKE_EXTRA_UNIX_TARGETS

Basically the idea is to reinvent the INSTALLS mechanism, but without the circular dependencies. I wrote a little Tcl script that takes the destination path, the name of a .pro file to generate and a list of headers. In my .pro file I put

HEADERS_PATH = $$INSTALLBASE/include/$${TARGET}_$${VERSION}/$${TARGET}
system(preinstallheaders.tcl $$HEADERS_PATH pih.pro $$HEADERS)
include(pih.pro)

And this would generate "pih.pro" containing

mkdir.commands = $$QMAKE_MKDIR [path to install to]
mkdir.target = [path to install to]
iheader0.target = [path to install to]/header.h
iheader0.commands = $$QMAKE_COPY src/header.h [path to install to]
iheader0.depends = src/header.h
...
iheaderX ...
unix:QMAKE_EXTRA_UNIX_TARGETS += mkdir iheader0 ... iheaderX
PRE_TARGETDEPS += [path to install to] [path to install to]/header.h ...

Not very pretty, but it got the job done. I put the "pih.pro" file in the working directory - where there would be some risk of overwriting if I built on two platforms simultaneously. One solution to this would be to put the file in a platform-specific directory. On top of that, when the project file includes pih.pro, the makefile will contain a dependency between pih.pro and itself. This means that if you go and build on a different platform, make will cause qmake to regenerate the makefile (and pih.pro), which is unnecessary.

Another thing that I never bothered to solve was if the files need to be installed to more than one directory, if I made two calls to preinstallheaders.tcl, they would both create extra targets starting with iheader0, so the second one would hide the third. This should be easy enough to solve, but as it only affected one library for me, it wasn't worth the effort.

All the same, I thought to myself that there must be something prettier. So ...

3. Use QMAKE_EXTRA_UNIX_COMPILERS

It's prehaps not the most obvious thing to do, use a 'compiler' to 'install' a header. But there you go. Here's an example.

install_headers.output = [path to install to]/${QMAKE_FILE_BASE}.h
install_headers.input = INSTALL_HEADERS
install_headers.commands = $$QMAKE_COPY ${QMAKE_FILE_NAME} $$make_dir.target
install_headers.CONFIG = no_link

QMAKE_EXTRA_UNIX_TARGETS += make_dir
QMAKE_EXTRA_UNIX_COMPILERS += install_headers

INSTALL_HEADERS = $$HEADERS $$GENERATED_HEADERS

PRE_TARGETDEPS += $$make_dir.target $$UIDIR $$GENERATED_HEADERS $$INST_HDRS $$INST_GEN_HDRS

A few comments are in order. Firstly, 'make_dir' is the same thing as in point 2 above. It's there to ensure that the destination directory exists before any attempt at installing files. There's no need for a .depends field. I use ${QMAKE_FILE_BASE}.h for the output since ${QMAKE_FILE_NAME} may include an undesirable relative path. make_dir is a target, just like mkdir in the pih.pro file in part 2 above. $$UIDIR needs to be in the PRE_TARGETDEPS before $$GENERATED_HEADERS, since that is where they get put. $$GENERATED_HEADERS needs to be before $$INST_GEN_HDRS since the headers need to be generated before they can be installed!

Copyright © Paul John Floyd 2005 - 2007, 2009

Valid HTML 4.01!