Build Systems

Building software reliably across different platforms can be a complex task. You will encounter different environments with different compilers, paths, and library variations. The purpose of Qt is to shield the application developer from these cross-platform issues. Qt relies on CMakeBuild Systems - 图1 (opens new window) to convert CMakeLists.txt project files to platform specific make files, which then can be built using the platform specific tooling.

TIP

Qt comes with three different build systems. The original Qt build system was called qmake. Another Qt specific build system is QBS which uses a declarative approach to describing the build sequence. Since version 6, Qt has shifted from qmake to CMake as the official build system.

A typical build flow in Qt under Unix would be:

  1. vim CMakeLists.txt
  2. cmake . // generates Makefile
  3. make

With Qt you are encouraged to use shadow builds. A shadow build is a build outside of your source code location. Assume we have a myproject folder with a CMakeLists.txt file. The flow would be like this:

  1. mkdir build
  2. cd build
  3. cmake ..

We create a build folder and then call cmake from inside the build folder with the location of our project folder. This will set up the makefile in a way that all build artifacts are stored under the build folder instead of inside our source code folder. This allows us to create builds for different qt versions and build configurations at the same time and also it does not clutter our source code folder which is always a good thing.

When you are using Qt Creator it does these things behind the scenes for you and you do not have to worry about these steps in most cases. For larger projects and for a deeper understanding of the flow, it is recommended that you learn to build your Qt project from the command line to ensure that you have full control over what is happening.

CMake

CMake is a tool created by Kitware. Kitware is very well known for their 3D visualization software VTK and also CMake, the cross-platform makefile generator. It uses a series of CMakeLists.txt files to generate platform-specific makefiles. CMake is used by the KDE project and as such has a special relationship with the Qt community and since version 6, it is the preferred way to build Qt projects.

The CMakeLists.txt is the file used to store the project configuration. For a simple hello world using Qt Core the project file would look like this:

  1. // ensure cmake version is at least 3.16.0
  2. cmake_minimum_required(VERSION 3.16.0)
  3. // defines a project with a version
  4. project(foundation_tests VERSION 1.0.0 LANGUAGES CXX)
  5. // pick the C++ standard to use, in this case C++17
  6. set(CMAKE_CXX_STANDARD 17)
  7. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  8. // tell CMake to run the Qt tools moc, rcc, and uic automatically
  9. set(CMAKE_AUTOMOC ON)
  10. set(CMAKE_AUTORCC ON)
  11. set(CMAKE_AUTOUIC ON)
  12. // configure the Qt 6 modules core and test
  13. find_package(Qt6 COMPONENTS Core REQUIRED)
  14. find_package(Qt6 COMPONENTS Test REQUIRED)
  15. // define an executable built from a source file
  16. add_executable(foundation_tests
  17. tst_foundation.cpp
  18. )
  19. // tell cmake to link the executable to the Qt 6 core and test modules
  20. target_link_libraries(foundation_tests PRIVATE Qt6::Core Qt6::Test)

This will build a foundations_tests executable using tst_foundation.cpp and link against the Core and Test libraries from Qt 6. You will find more examples of CMake files in this book, as we use CMake for all C++ based examples.

CMake is a powerful, a complex, tool and it takes some time to get used to the syntax. CMake is very flexible and really shines in large and complex projects.

References

QMake

QMake is the tool which reads your project file and generates the build file. A project file is a simplified write-down of your project configuration, external dependencies, and your source files. The simplest project file is probably this:

  1. // myproject.pro
  2. SOURCES += main.cpp

Here we build an executable application which will have the name myproject based on the project file name. The build will only contain the main.cpp source file. And by default, we will use the QtCore and QtGui module for this project. If our project were a QML application we would need to add the QtQuick and QtQml module to the list:

  1. // myproject.pro
  2. QT += qml quick
  3. SOURCES += main.cpp

Now the build file knows to link against the QtQml and QtQuick Qt modules. QMake uses the concept of =, += and -= to assign, add, remove elements from a list of options, respectively. For a pure console build without UI dependencies you would remove the QtGui module:

  1. // myproject.pro
  2. QT -= gui
  3. SOURCES += main.cpp

When you want to build a library instead of an application, you need to change the build template:

  1. // myproject.pro
  2. TEMPLATE = lib
  3. QT -= gui
  4. HEADERS += utils.h
  5. SOURCES += utils.cpp

Now the project will build as a library without UI dependencies and used the utils.h header and the utils.cpp source file. The format of the library will depend on the OS you are building the project.

Often you will have more complicated setups and need to build a set of projects. For this, qmake offers the subdirs template. Assume we would have a mylib and a myapp project. Then our setup could be like this:

  1. my.pro
  2. mylib/mylib.pro
  3. mylib/utils.h
  4. mylib/utils.cpp
  5. myapp/myapp.pro
  6. myapp/main.cpp

We know already how the mylib.pro and myapp.pro would look like. The my.pro as the overarching project file would look like this:

  1. // my.pro
  2. TEMPLATE = subdirs
  3. subdirs = mylib \
  4. myapp
  5. myapp.depends = mylib

This declares a project with two subprojects: mylib and myapp, where myapp depends on mylib. When you run qmake on this project file it will generate file a build file for each project in a corresponding folder. When you run the makefile for my.pro, all subprojects are also built.

Sometimes you need to do one thing on one platform and another thing on other platforms based on your configuration. For this qmake introduces the concept of scopes. A scope is applied when a configuration option is set to true.

For example, to use a Unix specific utils implementation you could use:

  1. unix {
  2. SOURCES += utils_unix.cpp
  3. } else {
  4. SOURCES += utils.cpp
  5. }

What it says is if the CONFIG variable contains a Unix option then apply this scope otherwise use the else path. A typical one is to remove the application bundling under mac:

  1. macx {
  2. CONFIG -= app_bundle
  3. }

This will create your application as a plain executable under mac and not as a .app folder which is used for application installation.

QMake based projects are normally the number one choice when you start programming Qt applications. There are also other options out there. All have their benefits and drawbacks. We will shortly discuss these other options next.

References