Skip to content

Modern CMake

CMake reads a project model and generates a build system for Ninja, Make, Visual Studio, Xcode, and other backends. It does not compile source itself. The durable unit of modern CMake design is a target with usage requirements.

Configure, generate, build, and test are distinct

sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install build --prefix staging

Configure evaluates CMakeLists.txt, finds dependencies, checks the toolchain, and fills a cache. Generate writes the backend build graph. cmake --build invokes that backend. Install copies declared artifacts into a layout.

Changing compiler in an existing build tree is unreliable because detection results are cached. Configure a fresh build directory for a new toolchain.

Targets carry requirements

cmake
cmake_minimum_required(VERSION 3.25)
project(Report VERSION 1.0.0 LANGUAGES C CXX)

add_library(report src/report.cpp)
target_include_directories(report
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)
target_compile_features(report PUBLIC cxx_std_20)

add_executable(report-cli src/main.cpp)
target_link_libraries(report-cli PRIVATE report)

Linking to report tells CMake both to link the artifact and propagate its public usage requirements. Do not globally add include directories and flags when they belong to one target.

PRIVATE affects only the target, PUBLIC affects the target and consumers, and INTERFACE affects only consumers. If a public header contains a dependency's types, that dependency is usually public; if only the .cpp uses it, it is private.

Generators shape configurations

Ninja and Make are commonly single-config: configure one build tree with CMAKE_BUILD_TYPE. Visual Studio, Xcode, and Ninja Multi-Config choose Debug or Release at build time:

sh
cmake --build build --config Release

Do not assume CMAKE_BUILD_TYPE exists in a Visual Studio build. Use generator expressions such as $<CONFIG:Debug> for configuration-dependent target properties when needed.

Presets record shared workflows

CMakePresets.json can version configure, build, test, and workflow presets. CMakeUserPresets.json can contain local inheritance and machine paths. Presets reduce undocumented command variations and integrate with IDEs.

Dependencies should produce targets

cmake
find_package(ZLIB REQUIRED)
target_link_libraries(report PRIVATE ZLIB::ZLIB)

An imported target carries artifact paths, include directories, definitions, and transitive dependencies. Old variable-based packages may expose ${ZLIB_LIBRARIES} and ${ZLIB_INCLUDE_DIRS}; recognize them but prefer targets when provided.

FetchContent can configure a source dependency as part of the project. Package managers can install binaries and provide CMake packages/toolchain integrations. CMake coordinates the build graph; it is not itself a universal package manager.

Install and export complete a library

cmake
include(GNUInstallDirs)

install(TARGETS report
  EXPORT ReportTargets
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT ReportTargets
  NAMESPACE Report::
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Report
)

A full package also supplies a configured ReportConfig.cmake and version file so consumers can call find_package(Report). Test the installed package from a separate consumer project; the build tree can hide missing install requirements.

Policies and minimum versions stabilize behavior

cmake_minimum_required selects policy behavior as well as requiring a tool version. Avoid copying obsolete commands from old tutorials. Target properties keep requirements attached to the library or executable that needs them, while directory-wide state can silently affect unrelated targets as a project grows.

Optional prompts

Explain: Why is target_link_libraries(app PRIVATE report) more than a linker command?

Answer: CMake links the artifact and propagates report's public/interface usage requirements such as include paths, definitions, features, and transitive targets.

Debug: CMAKE_BUILD_TYPE=Release seems ignored in Visual Studio. Why?

Answer: Visual Studio is multi-config; select the configuration with --config Release or the IDE configuration, rather than configuring one build type.

Further reference