A cookbook and instruction manual for writing CMake for Qt6 for cross-platform apps.
This is for anyone getting starting with Qt using CMake, or migrating from qmake to CMake. Hopefully it'll save you a lot of time and stress.
If you like it, please star it on Github and share it. If you find any issues or want to get in touch, create a Git issue or find me via the Qt Discord server. Thanks!
Note: My own production app is a Qt Quick app, so the cookbook architecture matches this use case.
The root level CMake file CMakeLists.txt
- sets up the project,
- adds in code modules as 'libraries',
- adds in the app itself, as an executable target that is also a QML module,
- adds in test modules that may make use of the libraries.
Qt provides a number of its own CMake commands. These have evolved and changed considerably since Qt 6.2 and the versions supported here are based on Qt 6.5. This repo has been tested against Qt 6.8.0.
I use qt_standard_project_setup provided in Qt6::Core to set up the project, which avoids the need to manually ensure auto-moc or auto-rcc, for example.
Each library, module, and even the executable target, sits in its own subdirectory in the filespace, has its own CMake file, and is added with the command add_subdirectory. By structuring it this way, the libraries can be made available to both the app and the tests.
Although a flatter structure would technically be possible, you would still need a separate library for each QML module, because of how Qt creates them. More on that below.
See CommonHeaders/CMakeLists.txt, which creates a C++ library that does not have a QML counterpart. These are the simplest libraries and contain all the elements needed for all libraries.
-
We use
qt_add_libraryto define the library and specify its source files (which can include header files, e.g..h, as well as source files, e.g..cpp).By convention, the name of the library is the name of the directory with
Libappended. e.g.CommonHeadersLibfor the library in theCommonHeadersdirectory. -
Source files in this directory need to be able to
#includeone another without any path, so we havetarget_include_directoriesfor this directory. (${CMAKE_CURRENT_SOURCE_DIR}is the path of the directory of this CMake file.) -
Any libraries that are
#included by the source files are linked usingtarget_link_libraries. In theCommonHeadersexample, we don't reference any of our other libraries, just Qt's Core module.
In the previous example, target_link_libraries only used the PUBLIC keyword. In general you will use PRIVATE and PUBLIC and their usage is important.
Any libraries following the PRIVATE keyword are only available to this library whereas any libraries following the PUBLIC keyword become available to a library that links to this one.
A simple rule of thumb is:
Use PRIVATE, where you#includea library's header in source file(s) and not in any of the header files.Use PUBLIC, for any other libraries you#include.
So in the case of ToDoListModel/CMakeLists.txt,
CommonHeadersLibis privately linked becauseMyCommonHeaders.his included in the.cppfile and not the.hfile, and so any libraries that useToDoModeldo not need to know about this link.Qt Quickis publicly linked because the header file has#include <QAbstractListModel>and any library using this one will need to understand theQAbstractListModelclass.ToDoObjectsis publicly linked because the header file has#include "ToDoObjects.h"andToDoListis the return type oflist(), so any library that uses this one will also need to understandToDoList.
A QML module is a special kind of library. Since Qt 6.2, Qt has made it easy to create a QML module using qt_add_qml_module.
If you're migrating from qmake or you've been used to creating QML modules before Qt 6.2, you'll be familiar with having to register each module in your main.cpp and ensuring you've created a qmldir. Fortunately qt_add_qml_module does all of this for you. But it doesn't come without its own idiosyncracies.
Let's consider a C++ class that extends QML. See ToDoListModel/CMakeLists.txt.
- We use
qt_add_libraryto define the library, but we do not specify any source files here. - We use
qt_add_qml_moduleto generate a QML module associated with the library, and this is where we specify the source files.- We need to set a
URI, in this caseToDoListModel. After building, this will enable you to useimport ToDoListModelin your QML files. - As of Qt 6.5, it is necessary to specify
RESOURCE_PREFIX. My personal choice is to set it to/. If you were to set it toRESOURCE_PREFIX /my/custom/paththen you'd have to useimport my/custom/path/ToDoListModelin QML. - Set the
VERSION. By default I always set this to1.0. Note that as of Qt 6 (6.2?), QMLimportstatements no longer need to specify the version, to use the latest version, so I see this as boilerplate code.
- We need to set a
- Now look at the C++. To make an item available to QML engine, you also need to declare it using one of the QQmlEngine macros in your C++.
#include <QtQml>to make those macros available.- The most common macro is
QML_ELEMENTwhich you apply to aQObjectderived class, alongsideQ_OBJECT. By doing this, its properties andQ_INVOKABLEfunctions become available in QML. SeeToDoListModel/ToDoListModel.h.
- Finally, we link the main app target to this library (see
app/CMakeLists.txt), again usingtarget_link_libraries.- Because we want it to access a QML module, not a C++ library, we link to
ToDoListModelLibplugininstead ofToDoListModelLib. ToDoListModelLibplugingets automatically generated for us byqt_add_qml_module, which applies the suffixplugin(all lowercase).
- Because we want it to access a QML module, not a C++ library, we link to
See ObjectTypes/CMakeLists.txt for a more complex example of a QML module that contains a QML file as well as a C++ class.
Configuring for iOS is not straightforward, it's poorly documented (by Qt and CMake) and is based on generating a very old format of Xcode project. As a result, if you just follow the documented instructions, you'll have to do a number of manual steps in Xcode each time you run CMake.
I have developed my own surefire approach that enables 99% configuration in CMake, 1% in the Info.plist and 0% in Xcode:
Thanks to Craig Scott and the CMake community for their tips along the way.