Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Nicolas Fella
18 November 2021
CMake is increasingly becoming the de-facto build system for C++ projects. While it has been possible to build Qt applications using CMake for a long time, with Qt6, Qt switched its own internal build system to CMake.
The KDE Community was among the first large, open-source projects that adopted CMake about 15 years ago. Over this time, a lot of experience with CMake has accumulated in the community and solutions for recurring problems have been implemented.
These solutions are available for everyone in the Extra CMake Modules framework, or ECM, for short.
ECM is available from many popular dependency managers, including Conan, Vcpkg, Yocto and, of course, directly from its git repository.
All you need to do to use it in your project is tell CMake to find it and adjust your CMAKE_MODULE_PATH:
find_package(ECM REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
Now you are ready to include() its modules. You can find a list of all available modules in the API documentation.
When writing a CMake buildsystem, one often writes custom find modules for integrating third-party dependencies. ECM comes with a number of find modules for various commonly used dependencies, including, for example, GLIB2, Gperf, Inotify, PulseAudio, QtWaylandScanner, wayland-protocols, XCB, TagLib, or EGL.
(Unit)Testing is indispensable for writing and maintaining software. Qt comes with a rich test framework. When using it, you typically create a separate executable for each test. In plain CMake, this comes with some boilerplate:
add_executable(test1)
target_sources(test1 PRIVATE test1.cpp)
target_link_libraries(test1 Qt::Core Qt::Test myapp)
add_executable(test2)
target_sources(test2 PRIVATE test2.cpp)
target_link_libraries(test2 Qt::Core Qt::Test myapp)
add_executable(test3)
target_sources(test3 PRIVATE test3.cpp)
target_link_libraries(test3 Qt::Core Qt::Test myapp)
ECM provides the ECMAddTests macro, which reduces this boilerplate:
include(ECMAddTests)
ecm_add_tests(
test1.cpp
test2.cpp
test3.cpp
LINK_LIBRARIES
Qt::Core Qt::Test myapp
)
An additional benefit of using ECMAddTest is that it enables Q_ASSERT when running the tests; it even does so when built in Release mode, where asserts are usually disabled. This helps your tests find issues in your code without having to build your project in Debug mode.
When developing a library, CMake provides a convenient way for other projects to use your library, in the form of CMake config files. However, you likely also want projects that don't use CMake to use your library. A popular build-system-agnostic way of exporting libraries is by using pkg-config. Your library would install a mylib.pc file that describes the compiler flags necessary (include paths, libraries, etc.) for using your library. ECM comes with ECMGeneratePkgConfigFile, an easy way to generate the needed .pc file for your project.
add_library(mylib)
target_link_libraries(mylib PUBLIC Qt::Core Qt::Widgets)
...
include(ECMGeneratePkgConfigFile)
ecm_generate_pkgconfig_file(BASE_NAME MyLib
INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/MyLib/
DEPS "Qt5Core Qt5Widgets"
INSTALL)
This installs MyLib.pc to the appropriate location.
You often want to define your project's version information in a central place in the build system and propagate the information to the source code, e.g. to make myapp --version work or show it in an "About MyApp" screen. CMake supports passing a VERSION argument to the project() call, which sets up some relevant CMake variables. A common way to pass these variables to the source code is to configure a version header. ECMSetupVersion offers a convenient way to do this:
cmake_minimum_required(VERSION 3.16)
project(mylib VERSION 1.2.3)
...
include(ECMSetupVersion)
ecm_setup_version(PROJECT VERSION_HEADER mylib_version.h)
...
install(FILES ${CMAKE_BINARY_DIR}/mylib_version.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
Now you can access this information from your C++ code:
#include "mylib_version.h"
...
qDebug() << "MyLib has version" << mylib_VERSION_STRING; // MyLib has version 1.2.3
This header also provides an expression that is compatible with the QT_VERSION_CHECK macro:
#if mylib_VERSION < QT_VERSION_CHECK(1, 1, 0)
myFunction();
#else
myOtherFunction();
#endif
Some features in ECM aim at making the interaction with Qt more pleasant.
Similarly to providing pkg-config files, you may want to deploy the necessary files to use your library from QMake. ECMGeneratePriFile takes care of that for you:
add_library(mylib)
target_link_libraries(mylib PUBLIC Qt::Core Qt::Widgets)
...
include(ECMGeneratePriFile)
ecm_generate_pri_file(BASE_NAME MyLib LIB_NAME MyLib DEPS "Core Widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/MyLib)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
Qt provides powerful support for categorized logging. However, defining the logging categories comes with some boilerplate. ECMQtDeclareLoggingCategory takes care of some of that boilerplate:
include(ECMQtDeclareLoggingCategory)
...
add_executable(myapp)
...
ecm_qt_declare_logging_category(myapp
HEADER logging.h
IDENTIFIER MYAPP
CATEGORY_NAME com.mycompany.myapp
)
All you need to do is include the generated header and you are good to go:
#include "logging.h"
...
qCDebug(MYAPP) << "Hello World!";
Now you can run QT_LOGGING_RULES="com.mycompany.myapp=true" myapp and enjoy your categorized logging.
It also provides integration with KDebugSettings, a graphical tool for dis/enabling logging categories.
These are only some of the functionalities provided by ECM. Check out the full list of modules!
This post is part of a series of posts about the KDE Frameworks. See Part 1 for an introduction to the KDE Frameworks in general and the KConfig Framework.
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.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more