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
Reference: Generating Dependency Graphs with CMake
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