Build System
Updated 2025-01-01T00:00:00.000ZCMake — Advanced Patterns
Advanced CMake — FetchContent, install/export, package config files, presets, generator expressions, and modern CMake best practices.
TL;DR
Use FetchContent for dependency fetching, install() + export() for distributable packages, and CMakePresets.json for reproducible build configurations. Prefer target-based CMake — never use global include_directories.
FetchContent — Dependency Management
cmake
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
GIT_SHALLOW TRUE # don't fetch full history
)
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_SHALLOW TRUE
)
# Fetch all at once (parallel download)
FetchContent_MakeAvailable(googletest nlohmann_json)
target_link_libraries(myapp
PRIVATE
nlohmann_json::nlohmann_json
)
target_link_libraries(mytest
PRIVATE
GTest::gtest_main
)Install and Export
cmake
# Install targets
install(TARGETS mylib myapp
EXPORT MyProjectTargets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
# Install headers
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
)
# Export targets for downstream CMake find_package()
install(EXPORT MyProjectTargets
FILE MyProjectTargets.cmake
NAMESPACE MyProject::
DESTINATION lib/cmake/MyProject
)
# Generate config file
include(CMakePackageConfigHelpers)
configure_package_config_file(
cmake/MyProjectConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake
INSTALL_DESTINATION lib/cmake/MyProject
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
DESTINATION lib/cmake/MyProject
)Config file template (cmake/MyProjectConfig.cmake.in)
cmake
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MyProjectTargets.cmake")
check_required_components(MyProject)Downstream usage
cmake
find_package(MyProject 1.0 REQUIRED)
target_link_libraries(app PRIVATE MyProject::mylib)CMakePresets.json
json
{
"version": 6,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined"
}
},
{
"name": "release",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON"
}
},
{
"name": "clang",
"inherits": "debug",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_C_COMPILER": "clang"
}
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "debug"
},
{
"name": "release",
"configurePreset": "release",
"jobs": 0
}
],
"testPresets": [
{
"name": "debug",
"configurePreset": "debug",
"output": { "verbosity": "verbose" }
}
]
}bash
cmake --preset debug
cmake --build --preset debug
ctest --preset debugGenerator Expressions
cmake
# Compile flags only for specific config
target_compile_options(mylib PRIVATE
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
# Different flags for different compilers
target_compile_options(mylib PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -Wshadow>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# BUILD vs INSTALL include paths
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Link only when target exists
target_link_libraries(myapp PRIVATE
$<TARGET_EXISTS:optional_dep>:optional_dep::optional_dep>
)Modern Target Properties
cmake
add_library(mylib STATIC src/mylib.cpp)
# Set C++ standard on the target (not globally)
target_compile_features(mylib PUBLIC cxx_std_20)
set_target_properties(mylib PROPERTIES
CXX_EXTENSIONS OFF # no GNU extensions
POSITION_INDEPENDENT_CODE ON
)
# Alias for consistent ::namespace naming
add_library(MyProject::mylib ALIAS mylib)
# Header-only library
add_library(headers INTERFACE)
target_include_directories(headers INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_features(headers INTERFACE cxx_std_20)Custom Commands and Code Generation
cmake
# Generate a file at build time
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/codegen.py
--output ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/schema.json
${CMAKE_CURRENT_SOURCE_DIR}/codegen.py
COMMENT "Generating code from schema"
)
add_library(mylib
src/real.cpp
${CMAKE_CURRENT_BINARY_DIR}/generated.cpp # include generated file
)
target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_BINARY_DIR})CTest Integration
cmake
include(CTest)
enable_testing()
add_executable(mytest test/test_main.cpp)
target_link_libraries(mytest GTest::gtest_main mylib)
# Register tests
include(GoogleTest)
gtest_discover_tests(mytest
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
PROPERTIES TIMEOUT 30
)
# Custom test command
add_test(NAME integration_test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/integration_test.sh
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
set_tests_properties(integration_test PROPERTIES
ENVIRONMENT "DB_URL=postgresql://localhost/testdb"
)Compiler Warnings as Errors
cmake
add_library(warnings INTERFACE)
target_compile_options(warnings INTERFACE
$<$<CXX_COMPILER_ID:GNU,Clang>:
-Wall -Wextra -Wpedantic
-Wno-unused-parameter
-Werror
>
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX
/wd4100 # unreferenced formal parameter
>
)
target_link_libraries(mylib PRIVATE warnings)
target_link_libraries(myapp PRIVATE warnings)Toolchain File (Cross-Compilation)
cmake
# toolchain-arm.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)bash
cmake -B build-arm \
-DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake \
-DCMAKE_BUILD_TYPE=ReleaseEdit on GitHubUpdated 2025-01-01T00:00:00.000Z