Appendix D: CMake #
This appendix teaches CMake using the build setup of this book as the running example. CMake is a build‑system generator. It reads a description of the project and produces files for a native build tool. The book uses the Ninja generator. Ninja is a fast, low‑level build tool that CMake drives.
Configure, generate, build, test #
CMake separates configuration from building. The configure step reads CMakeLists.txt files and records the choices you make. The generate step writes the Ninja files. The build step compiles the targets. The test step runs the registered tests.
The book drives all four steps with one command sequence:
cmake --preset dev
cmake --build build -j
ctest --test-dir build --output-on-failure
The first command configures and generates. The preset dev supplies the generator and the build directory. The second command builds every target with -j for parallel jobs. The third command runs every test and prints the output of failing tests. This sequence is the acceptance gate for the book. The script scripts/verify.sh runs it inside the Nix development shell.
The project file #
The root CMakeLists.txt opens with a minimum version and a project name:
cmake_minimum_required(VERSION 3.30)
project(tour_cpp26 LANGUAGES CXX)
The version 3.30 guarantees that the features the book uses exist. The project declares that it uses only the C++ language. Three lines fix the language standard:
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
The standard is C++26, strictly. CMAKE_CXX_STANDARD_REQUIRED ON refuses a compiler that cannot reach C++26. CMAKE_CXX_EXTENSIONS OFF forbids GNU extensions. The line set(CMAKE_EXPORT_COMPILE_COMMANDS ON) writes a compile_commands.json file that clangd and other tools consume.
Cache variables and options #
CMake stores configuration values in a cache. The option command declares a boolean cache variable with a default:
option(BOOK_SANITIZE "Run example tests under AddressSanitizer and UndefinedBehaviorSanitizer" ON)
option(BOOK_ENABLE_GAPS "Build examples for C++26 features without shipping Clang support" OFF)
option(BOOK_WERROR "Turn compiler warnings into errors for book_example targets" ON)
BOOK_SANITIZE turns the sanitizers on for example tests. It defaults to ON. BOOK_ENABLE_GAPS builds examples for C++26 features that lack shipping Clang support. It defaults to OFF. BOOK_WERROR turns warnings into errors for book_example targets. It defaults to ON. You override a default on the command line with -D:
cmake --preset dev -DBOOK_ENABLE_GAPS=ON
The CMakePresets dev preset #
A preset bundles configuration options under a name. The file CMakePresets.json defines the dev preset:
{
"name": "dev",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
The preset selects the Ninja generator. It places the build tree in the build directory beside the source. It sets the build type to RelWithDebInfo, which optimizes the code and keeps debug information. The preset also defines matching build and test presets, so cmake --build build -j and ctest --test-dir build work without extra flags.
Targets and subdirectories #
A target is a named unit of work. The command add_executable creates an executable target from a source file. The command add_subdirectory descends into a child directory and reads its CMakeLists.txt. The file examples/CMakeLists.txt calls add_subdirectory for each chapter directory:
add_subdirectory(ch01)
add_subdirectory(ch02)
Each chapter directory holds one CMakeLists.txt that registers its examples. For example, examples/ch01/CMakeLists.txt contains:
book_example(ch01_hello.cpp EXPECT "hello, world")
book_demo(ch01_dangling.cpp)
book_demo(ch01_overflow.cpp)
The names book_example and book_demo are functions that the book defines. They are not CMake built‑ins.
The book_example function #
The functions live in cmake/BookExample.cmake. The root CMakeLists.txt adds that directory to the module path and includes the file:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(BookExample)
The book_example function registers a real, fully‑checked example. Its body is:
function(book_example file)
cmake_parse_arguments(arg "" "EXPECT" "" ${ARGN})
_book_add_target(t "${file}")
target_compile_options(${t} PRIVATE ${BOOK_WARNING_FLAGS})
if(BOOK_WERROR)
target_compile_options(${t} PRIVATE -Werror)
endif()
if(BOOK_SANITIZE)
target_compile_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined)
target_link_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined)
endif()
add_test(NAME ${t}_run COMMAND ${t})
if(arg_EXPECT)
set_tests_properties(${t}_run PROPERTIES
PASS_REGULAR_EXPRESSION "${arg_EXPECT}")
endif()
endfunction()
The helper _book_add_target derives the target name from the file name and calls add_executable. The function attaches the warning flags, the -Werror flag, and the sanitizer flags. It registers a CTest test named <name>_run. When EXPECT is present, the test must match the given regular expression. The book_demo function registers a deliberately wrong example that compiles with warnings visible and no test. The book_gap function registers a C++26 feature target that builds only with BOOK_ENABLE_GAPS=ON.
The warning flags come from a list that includes the lifetime‑safety flag. The file probes the compiler with check_cxx_compiler_flag and check_cxx_source_compiles to discover which spelling of the lifetime‑safety analysis it accepts. It prefers -Wlifetime-safety and falls back to the experimental cc1 form.
Try this #
Open cmake/BookExample.cmake, compare book_demo to book_example, then run cmake --preset dev -DBOOK_SANITIZE=OFF to verify the build succeeds without sanitizer flags.