A build system is a program that helps you build complex C++ projects. Very few people consider it important to know how to use a build system, but all developers need to live with it and therefore it is necessary to know it.
Every C++ developer comes across a build system sooner or later and the reactions range from boredom to outright anger and frustration. In this guide, I set out to present the main features of qmake with concrete examples to help you understand already written files and to modify them without it affecting your health 😊.
Main concepts
The Qt libraries were conceived using their build system, called qmake. For a long time qmake has been closely linked to Qt libraries, but today there are also other build systems that “understand” Qt such as CMake. Qmake is still very popular and for the moment it is the build system with which Qt itself is compiled.
A qmake project file is the central point of a Qt application and contains:
- compilation information, for example it indicates which libraries must be linked in the final executable;
- the information not necessary for the compilation but which is useful during the project, for example which are the translation files or how the application is deployed.
A project file is declarative by nature, i.e. for the most part it will consist of variable assignments and little else. A minimal project is the following:
TEMPLATE = app
TARGET = myApp
QT = core
SOURCES += main.cpp
This project file describes a final executable (line 1), the name of the executable is myApp
(line 2), it uses only QtCore as library Qt (line 3) and the list of sources to compile consists only of the file main.cpp
(line 4). Running qmake on this file, we will obtain a Makefile that will simply compile main.cpp
and link it to the Qt version that qmake is connected to.
An important thing to be aware of is that qmake knows with which version of Qt it has been compiled and also knows the path where the Qts are installed, so it is able to generate a correct Makefile. Suppose we have two versions of Qt installed, 5.9 and 5.12. To “compile” the program with the “correct” version of Qt, simply use the right qmake.
Another fundamental program for Qt applications is the moc. Qmake analyses all the files present in the variable HEADERS
and launches the binary moc
if at least one class contains the Q_OBJECT
macro:
HEADERS += fileA.h fileB.h
Other important programs are lupdate
, lrelease
, rcc
and uic
. To ensure that they are launched correctly, it is necessary to list all the resource files, UI files and translation files in the project:
TRANSLATIONS += myapp_it.ts
RESOURCES += qml.qrc
UI += mywidget.ui
The UI files will be compiled with the UI compiler (uic)
, the resource files with rcc
while the translations will be updated by running lupdate
.
Develer is a certified Qt partner
Modules and libraries
To use additional modules besides QtCore it is possible to use the variable QT, such as:
QT += quick network sql
In this case we have specified that the application will use the Qt Quick, QtNetwork and QtSql modules. The values of these variables are indicated in the documentation of each Qt class, at the top of the page (for example)
The LIBS
and INCLUDEPATH
variables are used to specify dependence on a dynamic library. The directories containing the include files are specified in INCLUDEPATH
, while the path to the library and the name must be specified in LIBS
.
INCLUDEPATH += /path/to/include
LIBS += -L/path/to/library -lmylib
On large projects, it is necessary to also manage project dependencies, such as unit tests or libraries where the project is divided. For this purpose different values are used for the variable TEMPLATE
:
- subdirs: used to specify that the project is divided into sub-directories;
- lib: used to indicate a project that generates a dynamic library;
- staticlib: used to indicate a project that generates a static library.
Let’s take an example: suppose we have a project consisting of the main application (perhaps written in Qt Quick), a library and unit tests. Both the main application and the unit tests use the library. The project file will be this:
TEMPLATE = subdirs
SUBDIRS = lib \
app \
test
app.depends = lib
test.depends = lib
This syntax explicitly specifies dependencies between sub-directories for qmake to generate the correct build order. Alternatively, you can use CONFIG += ordered
, which states that the variables listed in SUBDIRS
must be filled in order. When using a subdirs type project it is essential that all the strings indicated in the SUBDIRS variable are actually subdirectories with respect to where the project file is located.
It is also possible to manage library dependencies outside the project directory, but the topic is complex and comprehensively covered in the official qmake documentation.
Conditional compilation
Another useful feature is conditional compilation, which is used to specify rules to be applied only if certain conditions are true.
Conditional compilation is very useful when the program must be multi-platform but Qt does not provide a certain functionality; an example would be to read the Wi-Fi signal strength of the network to which we are connected. The most elegant way to support Windows and Linux platforms is to create two different implementation files, one per platform and to use the project file to specify which one to compile on each platform:
HEADERS += wifi_power.h
win32 {
SOURCES += wifi_power_win.cpp
}
unix {
SOURCES += wifi_power_unix.cpp
}
The <pre>win32 {...}</pre> block
… only runs when using a qmake that has Windows as a target.
The unix
and win32
strings are predefined in qmake, but it is possible to custom create them. Each string in the CONFIG
variable becomes a testable value. For example, suppose we have a library used in different products, some with Wi-Fi hardware on board and others without. To enable the Wi-Fi functionality you can write in the project file:
wifi_supported {
HEADERS += wifi_configuration.h wifi_handler.h
SOURCES += wifi_configuration.cpp wifi_handler.cpp
}
and launch qmake like this:
qmake CONFIG+=wifi_supported myapp.pro
The CONFIG
variable can be set on the qmake command line, so it is possible to customise compilation of the library from outside the project file.
Conclusions
This guide describes the main features of qmake with concrete examples that I hope will help clarify in which cases they should be used. Qmake also provides other features, such as multiple condition tests at the same time, support for pkg-config or the ability to write custom test functions. Further details can be found in the qmake documentation and in the undocumented qmake.