Qt and CMake: The Past, the Present and the Future
January 27, 2021 by Jörg Bornemann | Comments
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
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.8 Now!
Download the latest release here: www.qt.io/download.
Qt 6.8 release focuses on technology trends like spatial computing & XR, complex data visualization in 2D & 3D, and ARM-based development for desktop.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.
Commenting for this post has ended.
That's really great, and I personally play with those new feature they same me many life !But what does that means for Qt users ? Are you finally going to give an official way (and real life example) of doing the following in cmake/Qt ?
Build for iOS/Android out of the box
QML modules that works on every platforms
We have an open issue about building iOS projects which is being worked on. I'm not sure what you're referring to when mentioning "Build for Android out of the box". Also, maybe you could elaborate on "QML modules that work on every platform".
For the cli something like:
qt create my-project
cd my-project
qt add qml-application my-application --ios --android
qt add qml-module my.module
Either for Android or for iOS, the journey is painful. The documentation is really poor for those platforms and only presents the minimal cases. It’s the same with QML module. It’s painful to create one, especially when you target multiple platforms.
There is a lack of an official way to do a more realistic application. Containing multiple libraries and QML modules (real ones with both QML and C++), which build on all major platforms (Windows, Linux, macOS, iOS and Android) and ne can be launch in one click from QtCreator.
The exemples and Qt modules are too tight to you own build system to be really useful in those case. Which make Qt really not friendly for medium to high level application out of the box. This requires a long time to go trough this and have something that should be updated every time Qt or Google or Apple have decided to change it (which happens too often).
Qt is really an interesting concept, but it’s really too abstract for now, and I think it loose some market of being such not as friendly as it should be.
If you want to have a point of comparaison, I suggest you to look at VueJS or React, as starting project here, or scaling projects is made really simple.
The point here, I think is mainly that the documentation lack of some developers guides that instruct how you should achieve such a multiplatform scale using cmake. For now the cmake documentation is nearly reduced to a list of available functions.
Also, it would make sense to me, that you provide cmake function to create complete projects easily. Function like
add_qt_application
,add_qml_module
function that handle all the job. Having this out of the Qt source in a separate open-source repository may be a plus.In addition to this, having a cli set of tools to generate a project structure and allow for quick edit of some tasks (like quicly adding a new qml module) doesn't appear to be an impossible task, and really add great value to the product. Again, look at what vuejs and react has in this direction.
What about build times? Any improvement?
What are your major pain points here? For the initial build not much changed. Incremental builds gained speed up from using ninja alone.
As my personal experience (I'm not working at Qt, I'm just a Qt enthusiastic), so far so good, improvements (like those made to AUTOMOC) greatly improved the build time. I have no metrics here, but simply that recompiling after a small change was really fast compared to before.
Great that you did this! Thanks
Hey, lots of good stuff! By the way, how about https://gitlab.kitware.com/cmake/cmake/-/issues/16776? Was it also fixed along the way?
No, that issue is unfortunately still open. But it's on our radar.
qmake is an amazing build tool, simple, readable, very user-friendly, you need minimal code to write in qmake to make things work, even very complex things. If you compare the same project written in qmake and cmake, ehm, but cmake is modern and new fame.
Another thing is, that if you will not write in qmake one year and you come back to the project, you are ready to go, but after one year of inactivity on a complex cmake project you will be lost.
But I'm not judging, it is only my personal view, I love cmake too, cmake is absolutely amazing piece of software, it is only a current situation, I agree with porting to cmake. And it is primarily the way how cmake manages dependencies that is simply the winner, among the other things.