Skip to content

KDE Frameworks – Part 2 Extra CMake Modules for Enhancing Your CMake Code

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.

Using Extra CMake Modules

ECM is available from many popular dependency managers, including Conan, Vcpkg, Yocto and, of course, directly from its git repository.

cmake, ECM

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.

Find Modules

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.

General Purpose Modules

Testing

(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.

pkg-config Integration

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.

Versioning

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

Qt Modules

Some features in ECM aim at making the interaction with Qt more pleasant.

QMake Integration

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})

Logging

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

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

Categories: CMake / KDAB Blogs / KDAB on Qt / QtDevelopment / Technical

Tags: / /
Leave a Reply

Your email address will not be published. Required fields are marked *