As most of you know by now, CMake will replace QMake in Qt 6 as the standard build system for Qt itself and for Qt applications.
QMake as the build system used in Qt 5 has lots of quirks and limitations. For Qt 6, we aim to use CMake as a standard 3rd party build system to build Qt itself. CMake is by far the most widely used build system in the C++ world, and better integration with it is sorely needed. We will continue to support our users on QMake, but not develop it further or use it to build the Qt framework itself.Lars Knoll, Technical Vision for Qt 6
I moved from QMake to CMake in the winter of 2017/2018. The transition was painful. I had known QMake for more than 15 years. CMake was new to me. Even simple things took ages in CMake. Fortunately Daniel Pfeifer, a CMake expert, worked on the same project for an e-bike startup and patiently answered my questions. My final farewell to QMake came on the next project, where I had to integrate a code generator into the build of a Qt application. I couldn’t get it working with QMake. So, I gave CMake a chance and got it working pretty quickly.
So, give CMake a fair chance. It will be a nuisance in the beginning as nearly every new tool. But soon it will become a good and reliable companion. CMake is not just a cross-platform build system generator but also provides a packaging tool (CPack), a testing tool (CTest) and a dashboard (CDash). Here is a list of resources I find useful for learning CMake and for working with CMake.
Just buy the book! Its value is a multiple of its 30 USD price. The book covers all of CMake in three parts.
- Part I: Fundamentals. The first part teaches you the CMake language: application and library targets, variables and their scopes, flow control, functions, properties and splitting up projects into subdirectories. This is normally enough to build your Qt application with some libraries.
- Part II: Builds in Depth. The second part explains how to generate release, debug and other configurations, how to add compiler and linker flags, how to generate and copy files during a CMake run, how to integrate toolchains (32-bit GCC, Raspberry Pi and Android) and how to handle versioning.
- Part III: The Bigger Picture. The third part goes beyond CMake. It explains how to run tests with CTest and how to show the results in a dashboard (CDash). Installing your applications, libraries and auxiliary files with CMake amounts to some calls to CMake’s install function. CPack enables packaging the installation files as simple archives, Qt IFW, WIX, NSIS, RPM, DEB, etc.
Every chapter ends with a section Recommended Practices. Craig illustrates with hundreds of well thought-out examples how CMake and its companion tools work. I can often copy one or two lines from the book and use them in my own CMake files – with little or no modifications. The book also contains a lot of useful tips for building Qt applications.
Craig updated his CMake book to cover version 3.17. He added a brand new chapter about Working With Qt (Chapter 30). I had the pleasure of reviewing the new Qt chapter. It is full of great advice and well thought-out examples – like the rest of the book. Although I have gained quite a bit of experience with CMake, I learned a few new tricks.
find_packagefor each Qt module separately may find Qt modules from different versions. It’s better to call it once for all modules.
- If you switch on
CMAKE_AUTOMOC, CMake will collate a file mocs_compilation.cpp, which includes all sources files generated by moc. This file can quickly become huge, takes a long time to compile, and becomes the bottleneck for compilation. Craig gives a solution with
qt5_wrap_cppand a custom target, where CMake generates a separate moc source file for each moc header.
- CMake provides the macros
qt5_add_translationto generate the .ts files and to compile the .ts files into .qm files, respectively. Of course, Craig gives an example CMakeLists.txt file how to build and deploy translations.
- CMake comes with deployment tools
androiddeployqtfor MacOS, Windows and Android. The best solution for Linux is to use the CMake install commands for CMake 3.14 or newer or the workaround with QtCreatorDeployment.txt as described here for older versions.
As you probably know by now, CMake is the default build system for Qt 6. This new chapter prepares you very well for the future. As in my review of the 5th edition, my verdict is: Just buy the book! It’s worth every penny. Bonus: If you own the book already, you’ll get all updates for free.
This talk is the foundation for quite a few of Manuel’s dos and don’ts (see above). It especially elaborates on the tip Imagine targets as objects. Mathieu’s talk will make the difference whether you end up in CMake hell or not. So you better watch it!
Mathieu’s core point is that you regard every library defined by
add_library as an object or module with a public and private interface. Clients depending on the library don’t have to know all the compiler and linker options required to build the library. These are implementation details best hidden from clients.
All the target properties have
PRIVATE keywords to control the visibility of compiler and linker options. If, for example, library A depends on library B only internally, you can write
target_link_libraries(A PRIVATE B)
Clients of library A won’t know anything of B. If you replace
PUBLIC or leave out the keyword, clients of A will see library B. Library B will be listed in the linker options of A. Similarly, you can specify which headers are visible to clients and which are not.
target_include_directories(A PUBLIC include) target_include_directories(A PRIVATE src)
Mathieu’s advice is to use
PRIVATE whereever possible to avoid polluting the global namespace. Otherwise, your CMake projects will quickly become unmaintainable.
target_link_libraries command above refers to a module B instead of linker options
-L/usr/local/lib -lB. The linker options are hidden inside module B. Library A uses the command
find_package(B) to address module B as an object.
find_package only works, if CMake finds a config package file BConfig.cmake.
If library B is a CMake project, you can generate most of the config package file with the command
install(EXPORT). Unless for simple projects, you need to extend the config package file a little bit. Craig gives a detailed example in section 25.7.1 of his book Professional CMake. If library B is not a CMake project, Mathieu describes how to write a hand-made finder (starting at position 39:22 in the video).
Follow the link and click on the title Effective Modern CMake to see the full post in its latest version. Manuel has compiled a list of 40+ dos and don’ts, which he maintains and extends regularly. The explanation for each item is short and to the point. If the explanation is too short, you can head over to Craig’s CMake book for more details. Here are my favourite items.
- Treat CMake code like production code. Keep your CMakeLists.txt files as simple as possible and improve them regularly.
- Define project properties globally. Variables, options or properties defined in the top-level CMakeLists.txt file are propagated to all subdirectories and included files. Examples are compiler warnings and flags for crossbuilds, for static-analysis builds or for adding license checks.
- Follow a naming convention for test names. A unique prefix of the test project names helps you to identify a set of tests to run with CTest.
- Imagine targets as objects. The functions
add_libraryare the constructors. Target properties like
target_link_librariesare the member variables.
Manuel’s list will save you considerable time, as you don’t have to figure out the dos and don’ts for yourself.
Kevin gives a compact and easy-to-follow introduction how to write CMakeLists.txt files with modern CMake (versions 3.x). Loyal readers will know most of the tips from the CMake special in Episode 4 of my newsletter.
Enabling AUTOMOC merges all the files generated by moc into one big source file (one-big-file approach). The alternative is to have moc generate a file for each header file containing a Q_OBJECT macro (many-small-files approach). Kevin saw a 5% speedup with the one-big-file approach over the many-small-files approach. The overhead of creating a new compile process for each moc file outweighs the gains from building individual moc files in parallel.
Craig Scott makes the opposite observation in Section 30.3.1 Moc in his book Professional CMake (6th Edition): “If the number of classes processed by AUTOMOC […] is very large, […] it can make larger builds less efficient, so it may be desirable to have the generated files compiled individually to take advantage of build parallelism and reduce resource requirements.”
My observations are more in line with Craig’s. However, I haven’t done any measurements. So, Kevin’s advice may depend on the concrete situation.
Yes, this is the same Daniel Pfeifer who patiently helped me with my first CMake steps. Many of Manuel’s dos and don’ts (see here) are extracted from Daniel’s talk.
Daniel covers a wide range of CMake topics from basics over best practices for real-life projects to packaging and testing in just under 90 minutes. His talk is packed with tons of invaluable advice but can be a bit overwhelming. I still need to stop the video regularly and read up on the topics in Craig’s Professional CMake book.
I have written four articles about CMake with a special focus on Qt.
CMake Cross-Compilation Based on Yocto SDK. I assume that you have created an SDK from your Yocto build. Then, I walk you through writing a toolchain file step by step. The toolchain file for an i.MX6 SoC is used during cross-building the Qt application.
Deploying Qt Projects to Embedded Devices with CMake. QtCreator allows you to cross-build a Qt application, deploy it to the target embedded system and run it there – all with one command. The QtCreator based on Qt 5.12 could not generate all the information required for deployment from the CMakeLists.txt files. I describe a workaround. Newer QtCreator and CMake versions are better integrated and don’t need the workaround any more.
Benefits of a Relocatable Qt. Starting with version 5.14, Qt is relocatable. I show how to relocate Qt from a build server to a developer PC and from the developer PC to the target system. The second step demands special treatment of rpaths, which CMake provides.
Creating Simple Installers with CPack. This post is a follow-up to my earlier post Benefits of a Relocatable Qt, where I showed how to relocate Qt from a build server to a developer PC and finally to an embedded system. Relocation became very easy with Qt 5.14. My new post fixes two problems. First, I replace the absolute install rpath by an rpath relative to the application’s directory. Second, I use CPack to create a gzipped tarball instead of doing that manually. CPack also allows to create RPM and DEB packages or UI installers like the Qt Installer Framework (Qt IFW).