devl.cz/ cmake

CMake

Basics

Create minimal CMakeLists.txt:

cmake_minimum_required(VERSION 3.9)
project(hello_world LANGUAGES CXX)

add_executable(${PROJECT_NAME} main.cpp)

install(TARGETS ${PROJECT_NAME} DESTINATION bin)

Configure out-of-source build:

mkdir build_dir
cmake -G Ninja -S . -B build_dir

Build the project:

cmake --build build_dir

More information:

Finding external libraries

Require SDL2:

find_package(sdl2 REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})

Alternatively, using pkg-config:

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)
target_include_directories(${PROJECT_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})

Adding support for non-standard library

Create cmake-modules dir in your project root and add it to CMakeLists.txt:

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules)

Create cmake-modules/FindLiquidFun.cmake:

# Try to find LiquidFun.
# Use liquidfun_ROOT_DIR to specify custom installation dir.
#
# Once done this will define:
#   liquidfun_FOUND
#   liquidfun_INCLUDE_DIRS
#   liquidfun_LIBRARIES
#   liquidfun::liquidfun target

find_path(liquidfun_INCLUDE_DIR
    NAMES Box2D/Box2D.h
    PATH_SUFFIXES liquidfun/Box2D
    PATHS ${liquidfun_ROOT_DIR})
mark_as_advanced(liquidfun_INCLUDE_DIR)

find_library(liquidfun_LIBRARY
    NAMES liquidfun
    PATH_SUFFIXES liquidfun/Box2D/build/Box2D/Release
    PATHS ${liquidfun_ROOT_DIR})
mark_as_advanced(liquidfun_LIBRARY)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(liquidfun DEFAULT_MSG
        liquidfun_LIBRARY liquidfun_INCLUDE_DIR)

if (liquidfun_FOUND)
    set(liquidfun_LIBRARIES ${liquidfun_LIBRARY})
    set(liquidfun_INCLUDE_DIRS ${liquidfun_INCLUDE_DIR})
    if (NOT TARGET liquidfun::liquidfun)
        add_library(liquidfun::liquidfun INTERFACE IMPORTED)
        set_target_properties(liquidfun::liquidfun PROPERTIES
            INTERFACE_LINK_LIBRARIES "${liquidfun_LIBRARIES}"
            INTERFACE_INCLUDE_DIRECTORIES "${liquidfun_INCLUDE_DIRS}")
    endif()
endif()

Then use it:

find_package(LiquidFun REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE liquidfun::liquidfun)

More information, extra modules:

Debugging

To print build commands, either set a flag:

set(CMAKE_VERBOSE_MAKEFILE ON)

Or call make with an argument:

make VERBOSE=1

To quickly print contents of some variable:

message(WARNING "Test: ${SDL2_INCLUDE_DIRS}")

To debug find_package prefixes:

set(CMAKE_FIND_DEBUG_MODE ON)

There are various debugging arguments to cmake command:

-Wdev

Enable developer warnings.

--warn-uninitialized

Warn when using an uninitialized variable.

--debug-output

Put cmake in a debug mode.

--trace

Print a trace of all calls made and from where.

Trace some target properties, e.g. INCLUDE_DIRECTORIES:

set(CMAKE_DEBUG_TARGET_PROPERTIES INCLUDE_DIRECTORIES)

Watch a single variable for changes

Watch the CMake variable for change. May run a command on each change.

variable_watch(CMAKE_CXX_FLAGS)

Reference: variable_watch

Draw dependency graph using Graphviz

cmake --graphviz=deps.dot ..
dot -T png -o deps.png deps.dot

Adding custom options

Define the option:

option(WithPython "Build with embedded Python." ON)

Then set it in configuration phase:

cmake -DWithPython=OFF

Checking Target Platform

Use CMAKE_SYSTEM_NAME to check target platform:

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# ...
endif()

This is cross-compile friendly - it checks the system for which we’re building. The system on which we’re building is CMAKE_HOST_SYSTEM_NAME.

There are also some shortcuts:

  • ANDROID - Android, subset of Linux

  • APPLE - any Apple platform (macOS, iOS, …​)

  • UNIX - any UNIX-like platform (Linux, Apple, Cygwin, …​)

  • WIN32 - any Windows platform

These shortcuts are much easier to type:

if(APPLE)
# ...
endif()

We can also define our own shortcuts:

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(LINUX True)
endif()

Note that CMake tests the target platform by compiling a source file with macros like #if defined(APPLE). This source file can be found in CMakeFiles.

References:

Selecting Custom Compiler

Well-known variables like CC, CXX work for CMake too:

CC=clang CXX=clang++ cmake ..

Tips

NO_SYSTEM_FROM_IMPORTED

By default, includes from IMPORTED targets will use -isystem. This option disables this behaviour. IMPORTED targets are the norm for find_package modules (and configs) - they are by no means special, so they shouldn’t use special include style.

In some cases, the -isystem includes can even break the build, because they are prepended before all other include paths.

set(CMAKE_NO_SYSTEM_FROM_IMPORTED ON)

Glossary

CMLs

an abbreviation of CMakeLists.txt