Qt and CMake: The Past, the Present and the Future

We made a big decision to start using CMake to build Qt 6 one and a half years ago. The main reason for the decision was user feedback. Most Qt users wanted to have easier integration of their Qt projects with the rest of their software. According to the studies at that time, CMake was clearly the most commonly used build tool among Qt users - qmake aside. In addition, migrating to CMake gave us an opportunity to get rid of the maintenance burden of our internal build tools. 


Even bigger than the decision was the effort required to migrate to CMake.  Now the essential migration work has been completed and it's time to share our findings.

Although well established and used by many projects, CMake was missing some key features that were supported in the previous Qt build tools. That's why we collaborated with Kitware to get the obstacles out of the way and improve CMake for the benefit of the Qt Project and the larger CMake community.

Building Qt is complex, due to various contributing points:

  • Qt supports many, many platforms.
  • Qt is split into modules that can be built stand-alone or all-in-on.
  • Qt supports different ways of building it (prefix, non-prefix, different feature sets)

Let's have a look of the CMake improvements that Qt's build tool switch caused or influenced.

Precompiled Headers

In a C++ project, the same header files are potentially included over and over again. That is especially true for library headers. If the toolchain supports it, one can speed up compilation by precompiling header files.

For the longest time, CMake did not offer out-of-the-box support for that. But the days of searching the web for snippets to enable precompiled headers in CMake are over. Since CMake 3.16, there's the target_precompile_headers command that lets you add a list of header files to precompile.

See the original announcement for details.

Since: 3.16
Documentation: https://cmake.org/cmake/help/latest/command/target_precompile_headers.html

Unity Builds

Another way to speed up compilation is unity builds. This is the Technique of the Many Names - it is also known as jumbo build, amalgamated build and single compilation unit.

This technique creates one source file that includes all other source files, and only that one source file is compiled:

#include "source_file1.cpp"
#include "source_file2.cpp"
/*...*/
#include "source_file8.cpp"

The includes of all source files are processed only once, everything ends up in one translation unit, and the optimizer has a global view on the project.

However, not every C++ project can utilize unity builds without modification.  Follow the links in the description of the original CMake issue #19526 to learn more.

Since: 3.16
Documentation: https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html

depfile support for AUTOMOC + Ninja

There were longstanding complaints about CMake's AUTOMOC, that it would run needlessly or that it wouldn't detect properly when to call moc again. One reason for this was that the exact dependencies of moc's output were invisible to AUTOMOC.

Since Qt 5.15, moc learned to write out the exact files that form the dependencies of moc's output. And CMake 3.17 learned to read moc's depfiles and use it for the Ninja generator.

To sum it up: with Qt >= 5.15 and CMake >= 3.17 with the Ninja generator, AUTOMOC knows the right dependencies to re-run moc at the right time.

Since: 3.17
See CMake issue #19058. Led to fixing #17750 and #18749 too.

Ninja Multi-Config

On Windows and macOS, Qt was traditionally built in two configurations, debug and release, but in one build directory.

CMake did not offer a way to do this until version 3.17 introduced a new generator called "Ninja Multi-Config", which can build multiple configurations at once.

Since: 3.17
Documentation: https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html#generator:Ninja%20Multi-Config

iOS multi-architecture builds

Qt for iOS offers simulator-and-device builds that combine a Qt built for the actual target with a Qt built for the iOS simulator. While CMake 3.17 introduced Ninja Multi-Config, its iOS support was not quite ready yet for iOS multi-architecture builds.

CMake 3.18 fixed this, and we can happily build for the simulator and the device.

Since: 3.18
See the issues #19534, #20534 and #20588 for details.

file(CONFIGURE)

In the Qt build there are quite some files generated at configure time with dynamically created content. The configure_file command however, only consumes input files, no strings.

One way to solve this is to have an input file with only one variable

 --conf-file-content.txt.in--
@my-conf-file-content@

and call configure_file like this

set(my-conf-file-content "This is the generated content!")
configure_file("my-conf-file-content.txt.in" "output.txt" @ONLY)

In CMake 3.18, we can achieve the same much easier:

file(CONFIGURE OUTPUT "output.txt" CONTENT "This is the generated content!")

Since: 3.18
Documentation: https://cmake.org/cmake/help/latest/command/file.html?highlight=file#configure
See CMake Issue #20388

Eval! In League with Functions!

In the Qt build we exhaust the CMake language's ability to perform as an actual programming language. There's not only the CMake functions that are supposed to be called by the user, but a much more complicated machinery under the hood.

One thing that bothered us quite a bit is that it was impossible to call functions dynamically. In qmake, calling a function that is defined in a variable is possible, and the Qt5 build uses this ability extensively

f = message
$${f}("Hello World!")

There are ways to make this work in CMake, involving generating a file that's then included, but especially on Windows this would slow down the project configuration step immensely.

CMake 3.18 comes with cmake_language(EVAL) to evaluate CMake code and cmake_language(CALL) to call macros or functions.

set(f "message")
cmake_language(CALL ${f} STATUS "Hello World!")
cmake_language(EVAL CODE "${f}(\"Hello World!\")")

Since: 3.18
Documentation: https://cmake.org/cmake/help/latest/command/cmake_language.html
See CMake issue #18392

Call me later - deferred code

Again, a qmake feature inspired a CMake command.

In a QMake project, one can write CONFIG += foo, which in turn loads mkspecs/features/foo.prf after the actual project file is processed.

This is particularly useful for user-facing API. Assume, you're creating a project for Android.

qt_add_executable(MyApp)
set_property(MyApp TARGET PROPERTY QT_ANDROID_EXTRA_LIBS SuperDuperLib)
qt_finalize_executable(MyApp)

The qt_finalize_executable call generates proper deployment settings for the target, depending on the target's properties. If the user forgets to call qt_finalize_executable, the deployment settings are not generated, and there's no error nor warning.

Note: That's an example. The real API isn't that error prone. See the commit message of b94b7687b0635ee74a3ccd83a234ead0600fd47f how we approach this.

But wouldn't it be great if the user was liberated from the burden of calling qt_finalize_executable themselves? With CMake 3.19, the Qt build utilizes the new command cmake_language(DEFER CALL). With that, one can call functions at defined times, for example, after the project files of the current directory are evaluated.

Since: 3.19
Documentation: https://cmake.org/cmake/help/latest/command/cmake_language.html#defer
See CMake issue #19575

Properties on source files for different directory scopes

In certain places, we create a target for a module, and add source files to that target in a subdirectory.  This keeps the names of source files close to the CMakeLists.txt file for them.

However, one could not specify per-source file properties in those locations - we have to go up to the top level of the hierarchy to set those.  We do not do this often, but it sometimes comes up during development to allow compile-time selection of experimental features based on option settings.

In CMake 3.18, set_source_files_properties learned to set properties on different directory scopes:

The DIRECTORY option takes a list of paths pointing to processed source directories, and TARGET_DIRECTORY takes a list of targets, whose source directories will be used as the list of scopes where to set the source file properties.

get_property() and get_source_file_property() also got the same new arguments, except only one value can be specified instead of a list.

Since: 3.18
Documentation: https://cmake.org/cmake/help/latest/command/set_source_files_properties.html
See CMake issue #20128

add_custom_command DEPFILE support for Makefiles

CMake 3.20 will provide depfile support for the Unix Makefile generator. This is a premise for using moc-generated depfiles for Makefiles too.

Since: 3.20
See CMake issue #20286

Closing Remarks

The work isn't done yet. CMake is constantly improved, and the porting effort of more and more Qt modules to CMake will most probably spawn some more ideas.

Please post issues you find or suggestions you have in our bug tracker.


Blog Topics:

Comments