Using CMake with Qt 5 | KDAB (original) (raw)
CMake is a buildsystem generator developed in the open, and widely used for Qt based development. Especially when creating large or complex software, CMake can be more suitable to use than QMake. KDE was even the tipping point for the popularity of CMake in general, and with Qt 4 in particular, according to Bill Hoffman. KDAB engineers have contributed some features to CMake to ensure that the Qt 5 support for CMake is as good as possible.
KDAB contributed some new CMake Config files for Qt 5 to ensure that the integration between Qt and CMake can be even better. The updated documentation for using CMake with Qt 5 is has been reviewed and generated and repeats the relevant information in this blog post.
Finding Qt5
One of the major changes when using CMake with Qt is the result of increased modularity in Qt itself. Whereas when finding Qt 4, with CMake you use
find_package(Qt4 COMPONENTS QTCORE QTGUI)
To find Qt 5 you can find all of the modules you wish to use with separate commands:
find_package(Qt5Widgets)
find_package(Qt5Declarative)
There is likely to be a way in the future to specify the specific modules in one command, but this will not be available with Qt 5.0:
find_package(Qt5 COMPONENTS Widgets Declarative)
Building Qt5 projects with CMake
Once the package has been found, Qt 4 users would use the CMake variables ${QT_INCLUDES}
to set the include directories while compiling, and ${QT_LIBRARIES}
or ${QT_GUI_LIBRARIES}
while linking. Users of CMake with Qt 4 may have also used the ${QT_USE_FILE}
to semi-automatically include the required include directories and required defines.
With the modular Qt 5 system, the variables will instead be ${Qt5Widgets_INCLUDE_DIRS}
, ${Qt5Widgets_LIBRARIES}
, ${Qt5Declarative_INCLUDE_DIRS}
, ${Qt5Declarative_LIBRARIES}
etc for each module used.
This is a source-incompatibility in your CMake based buildsystem which will affect porting from Qt 4 to Qt 5. Luckily though, it is easy to add source compatibility back to the CMake variables and macros using some simple variable mappings.
Building executables with Qt 5 is slightly more complex than with Qt 4. One of the changes to how Qt 5 is built and packaged compared to Qt 4 is that the -reduce-relocations
configure option became the default. The effect of this is that compilations are run with the -Bsymbolic-functions
option, which makes function pointer comparison ineffective, unless the -fPIE flag is also supplied when building executables, or -fPIC
when building libraries for position independent code.
If Qt is configured manually, it is of course possible to configure with -no-reduce-relocations
and avoid the issue, but the default will be a requirement for all third parties to add compiler flags for position independent code. This can be done in the normal way with CMake:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}
${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
There is a Qt5<module>_EXECUTABLE_COMPILE_FLAGS
variable for each module available in Qt 5 which expands to either -fPIE
or the empty string, depending on how Qt was configured. Hoewever, the -fPIE
flag is really only for executables and should not be used for libraries.
Setting -fPIC
globally even when building executables may also work sufficiently, but shouldn't be the first option.
set(CMAKE_CXX_FLAGS "-fPIC")
Together with some other newer features in CMake, such as automated moc invokation, a simple CMake based build system using Qt 5 which is roughly equivalent to a Qt 4 buildsystem will look something like this:
cmake_minimum_required(2.8.7)
project(hello-world)
# Tell CMake to run moc when necessary:
set(CMAKE_AUTOMOC ON)
# As moc files are generated in the binary dir, tell CMake
# to always look for includes there:
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Widgets finds its own dependencies (QtGui and QtCore).
find_package(Qt5Widgets REQUIRED)
# The Qt5Widgets_INCLUDES also includes the include directories for
# dependencies QtCore and QtGui
include_directories(${Qt5Widgets_INCLUDES})
# We need add -DQT_WIDGETS_LIB when using QtWidgets in Qt 5.
add_definitions(${Qt5Widgets_DEFINITIONS})
# Executables fail to build with Qt 5 in the default configuration
# without -fPIE. We add that here.
set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
add_executable(hello_world main.cpp mainwindow.cpp)
# The Qt5Widgets_LIBRARIES variable also includes QtGui and QtCore
target_link_libraries(hello_world ${Qt5Widgets_LIBRARIES})
You can see though that there is a lot of repetition in the snippet, and a lot of things to take care of using.
The Qt5Widgets_EXECUTABLE_COMPILE_FLAGS
use is particularly forgettable, and even difficult to get right - it should not be used if building shared libraries for example, so it requires some maintenance if that is required within the same scope as building an executable.
There is even a subtle bug which people who have unit tested their Qt 4 core-only code will know. If the -DQT_GUI_LIB
definition is set (which happens if using QT_USE_FILE
for example), all unit tests need to link to QtGui, even though it is not used by the tests.
That is because of some magic in the header files of QtTest. The workaround is either careful scoping of targets and directories, or tricky manual manipulation of the definitions or variables controlling how Qt 4 is found.
Towards more modern Qt5 CMake usage
Starting with CMake 2.8.8, we can do a lot better:
cmake_minimum_required(2.8.8)
project(hello-world)
# Tell CMake to run moc when necessary:
set(CMAKE_AUTOMOC ON)
# As moc files are generated in the binary dir, tell CMake
# to always look for includes there:
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Widgets finds its own dependencies.
find_package(Qt5Widgets REQUIRED)
add_executable(hello_world main.cpp mainwindow.cpp)
qt5_use_modules(hello_world Widgets)
The qt5_use_modules
CMake function encapsulates all of the set-up required to use a Qt module. It can be used with multiple arguments at once for brevity, such as:
qt5_use_modules(hello_world Widgets Declarative)
This is similar to how qmake operates:
TARGET = hello_world
QT += widgets declarative
All properties are scoped to the particular target the function is used with, instead of being scoped to the CMakeLists.txt file it appears in, and affecting all libraries and executables.
For example in this snippet:
add_executable(hello_world main.cpp mainwindow.cpp)
add_library(hello_library lib.cpp)
add_executable(hello_coretest test.cpp)
find_package(Qt5Widgets)
qt5_use_package(hello_world Widgets)
qt5_use_package(hello_library Core)
qt5_use_package(hello_coretest Test)
Because all of the settings are scoped to the target (executable or library) they operate on, the -fPIE
doesn't get used when building the hello_library
library, and the -DQT_GUI_LIB
doesn't get used when building hello_coretest
.
It's a much cleaner way to write CMake based build systems.
Looking forward, we expect a similar but more powerful feature to be possible in upstream CMake too, so that you can expect to be able to use a similar function with any CMake package.
Implementation details for your Qt project
One of the features of CMake that many developers who used it will be familiar with is Find files. The idea is to write a Find file
for each dependency your project depends on, or use an existing Find file
provided elsewhere. CMake provides a large set of Find files
already, and KDE is preparing to make the Find files
developed through the years available to all users of CMake.
One of the Find files
provided by CMake is the FindQt4.cmake file. This file is responsible for finding Qt on your system so that you can invoke:
That Find file makes available the variables ${QT_INCLUDES}
to set the location of header files, and ${QT_QTGUI_LIBRARIES}
for linking, for example.
One of the disadvantages of this file being part of CMake, and not part of Qt, is that the file could get out-of-date. For example, when Qt 4.6 was released in December 2009 it included a new QtMultimedia module. Support for that module was not complete until CMake 2.8.2, released in June 2010.
That means that if someone wanted to use QtMultimedia, they could either have to wait for and then depend on the new CMake release, or attempt to copy the Find file
into their project to work with their existing CMake version. The copying may not even work if the updated Find file
uses features of the newer CMake.
Behind the scenes, Qt 5 is now found in a slightly different way too. Apart from making it possible to find your dependencies using a Find file
, CMake is also able to read files provided by the dependency itself to locate the libraries and header files. Such files are called Config files
, and usually they are generated by CMake itself.
A Qt 5 build, will also generate the CMake Config files
, but without causing Qt 5 to depend on CMake itself.
The primary benefit of this is that the features of Qt (and the modules of Qt) which can be used with CMake do not depend on the version of CMake being used. All Qt Essentials Modules and Qt Addons Modules will create their own CMake Config file
, and the features provided by the modules will be made available through the CMake macros and variables immediately.
As new modules become available, they will also be usable with CMake as soon as they are checked into a repository, so experimenting or prototyping can begin immediately and does not have to wait for a new CMake release.
Another benefit of using Config files
instead of find files is that the config files have access to all aspects of the build of Qt itself including how it was configured and where it is installed to, which helps keep complexity under control when supporting static builds, and cross compiling for example.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.