C++ CMake Cheat Sheet
Covers CMakeLists.txt basics, target-based library linking, common build-system variables, and the CLI workflow for configuring and building.
Minimal CMakeLists.txt
The smallest project file to build an executable.
cmake_minimum_required(VERSION 3.20)project(MyApp VERSION 1.0 LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)add_executable(myapp main.cpp src/helper.cpp)target_include_directories(myapp PRIVATE include)
Libraries & Linking
Build a library and link it into an executable target.
add_library(mathutils STATIC src/mathutils.cpp)target_include_directories(mathutils PUBLIC include)add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE mathutils)# Find and link an external packagefind_package(ZLIB REQUIRED)target_link_libraries(myapp PRIVATE ZLIB::ZLIB)# Fetch a dependency at configure time (CMake 3.14+)include(FetchContent)FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 10.1.1)FetchContent_MakeAvailable(fmt)target_link_libraries(myapp PRIVATE fmt::fmt)
Configure & Build (CLI)
Out-of-source build workflow from the command line.
cmake -S . -B build # configure into ./buildcmake -B build -DCMAKE_BUILD_TYPE=Release # set build typecmake --build build # build (calls make/ninja under the hood)cmake --build build --target myapp -j 8 # build a specific target, 8 jobsctest --test-dir build # run tests registered via enable_testing()cmake --install build --prefix /usr/local # install
Common Variables & Commands
Frequently used CMake variables and directives.
- CMAKE_BUILD_TYPE- Debug, Release, RelWithDebInfo, or MinSizeRel; controls optimization/debug flags.
- CMAKE_CXX_STANDARD- Sets the required C++ standard, e.g. 17 or 20.
- target_link_libraries- Links a library to a target; scope keyword PRIVATE/PUBLIC/INTERFACE controls propagation to dependents.
- target_include_directories- Adds header search paths to a target with the same PRIVATE/PUBLIC/INTERFACE scoping.
- add_subdirectory- Includes another directory's CMakeLists.txt, e.g. for a nested library or test folder.
- enable_testing / add_test- Registers tests runnable via ctest.
Generator Expressions
Defer configuration-specific values until CMake generates the final build files.
target_compile_definitions(myapp PRIVATE $<$<CONFIG:Debug>:DEBUG_BUILD> $<$<CONFIG:Release>:NDEBUG>)target_compile_options(myapp PRIVATE $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra> $<$<CXX_COMPILER_ID:MSVC>:/W4>)# only add sanitizer flags for Debug buildstarget_link_options(myapp PRIVATE $<$<CONFIG:Debug>:-fsanitize=address>)# select an include path relative to build vs install treetarget_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>)
install() & Exporting a find_package Config
Package a library so downstream projects can consume it via find_package().
include(GNUInstallDirs)install(TARGETS mathutils EXPORT mathutilsTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})install(EXPORT mathutilsTargets FILE mathutilsTargets.cmake NAMESPACE mathutils:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathutils)include(CMakePackageConfigHelpers)write_basic_package_version_file( mathutilsConfigVersion.cmake VERSION 1.0.0 COMPATIBILITY SameMajorVersion)# consumers then just: find_package(mathutils REQUIRED)
add_custom_command / add_custom_target
Run arbitrary build steps such as code generation before compiling a target.
add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/generated/proto.pb.cc COMMAND protoc --cpp_out=${CMAKE_BINARY_DIR}/generated ${CMAKE_SOURCE_DIR}/schema.proto DEPENDS ${CMAKE_SOURCE_DIR}/schema.proto COMMENT "Generating protobuf sources")add_custom_target(generate_proto DEPENDS ${CMAKE_BINARY_DIR}/generated/proto.pb.cc)add_executable(myapp main.cpp ${CMAKE_BINARY_DIR}/generated/proto.pb.cc)add_dependencies(myapp generate_proto)# run a step after a target is builtadd_custom_command(TARGET myapp POST_BUILD COMMAND ${CMAKE_STRIP} $<TARGET_FILE:myapp>)
CMakePresets.json
Share reproducible configure/build/test invocations across a team and CI without wrapper scripts.
{ "version": 6, "configurePresets": [ { "name": "linux-release", "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "CMAKE_CXX_STANDARD": "20" } } ], "buildPresets": [ { "name": "linux-release", "configurePreset": "linux-release", "jobs": 8 } ], "testPresets": [ { "name": "linux-release", "configurePreset": "linux-release", "output": { "outputOnFailure": true } } ]}// usage: cmake --preset linux-release && cmake --build --preset linux-release && ctest --preset linux-release
Advanced CMake Concepts
Mechanisms used once a project outgrows a simple single-executable build.
- INTERFACE library- A target with no build output of its own (e.g. header-only libs); usage requirements propagate to consumers via target_link_libraries.
- Toolchain file- A -DCMAKE_TOOLCHAIN_FILE=... script that sets the compiler, sysroot, and flags for cross-compiling (e.g. to ARM or WebAssembly).
- ExternalProject_Add- Downloads and builds a dependency as a separate CMake project at build time, unlike FetchContent which configures it into the same build.
- target_compile_features- Requests a specific language feature (e.g. cxx_std_20) rather than a whole standard version, letting CMake pick the minimum sufficient standard.
- CACHE variables- Variables persisted in CMakeCache.txt and overridable via -D on the command line, e.g. option(BUILD_TESTS "..." ON).
- CMAKE_EXPORT_COMPILE_COMMANDS- Emits compile_commands.json for clangd/IDE tooling and static analyzers.
- ccache integration- Set CMAKE_CXX_COMPILER_LAUNCHER=ccache to transparently cache object files and speed up repeated builds.
Always prefer target_* commands (target_include_directories, target_link_libraries, target_compile_definitions) with PUBLIC/PRIVATE/INTERFACE scoping over the old global include_directories()/link_libraries() - it keeps usage requirements properly scoped as your dependency graph grows.