A guide to Cmake

CMake execution flow chart#

graph TD
    A[Start] --> B["Write CMakeLists.txt Files"]
    B --> C["Run CMake Command (e.g., cmake .)"]
    C --> D{"CMakeLists.txt Valid?"}
    D -- No --> E["Error Message
Fix Configuration"] D -- Yes --> F["Parse CMakeLists.txt"] F --> G["Check System Dependencies
(Compiler, Libraries)"] G --> H{"All Dependencies Found?"} H -- No --> I["Error Message
Missing Tools/Libraries"] H -- Yes --> J["Generate Build System Files
(Makefiles, .sln, Xcode Projects)"] J --> K["Build System Ready"] K --> L["Run Build Tool
(make, ninja, MSBuild)"] L --> M{"Compilation
Successful?"} M -- No --> N["Fix Code/Configuration"] M -- Yes --> O["Generate Executables/Libraries"] O --> P[End]

Three Main Stages#

Configurations#

  1. Parsing CMakeLists.txt: CMake reads your build description files and executes the commands sequentially
  2. System inspection: Checks for available compilers, tools, and libraries on the system
  3. Dependency resolution: Identifies and locates required external packages
  4. Variable evaluation: Sets up build variables and processes conditionals

Generations#

  1. Build system selection: CMake determines which generator to use (Make, Ninja, Visual Studio, etc.)
  2. File generation: Creates all necessary files for the selected build system
  3. Dependency graph construction: Maps out how targets depend on each other.

Build#

  1. Compilation: Source files are compiled into object files
  2. Linking: Object files are linked together to form executables or libraries
  3. Post-build steps: Any custom commands or installation rules are executed

Core CMake Concepts#

Targets and Properties#

In modern CMake, everything revolves around targets (executables, libraries, custom outputs) and properties associated with those targets:

text
# Create a library target
add_library(MyLib SHARED src/lib.cpp src/utils.cpp)

# Set properties on the target
target_include_directories(MyLib
PUBLIC include # Headers for users of the library
PRIVATE src # Internal headers
)
target_compile_features(MyLib PUBLIC cxx_std_17)

Properties can have different visibility levels:

  • PRIVATE: Used only by the target itself
  • PUBLIC: Used by both the target and any target that links to it
  • INTERFACE: Used only by targets that link to this one8

Find Packages and Dependencies#

CMake provides a powerful mechanism for finding and using external libraries:

text
# Find an external package
find_package(Boost REQUIRED COMPONENTS system filesystem)

# Link against it
target_link_libraries(MyApp PRIVATE Boost::system Boost::filesystem)

This approach works with CMake’s built-in find modules and package config files provided by libraries8.

Advanced CMake Topics#

Generator Expressions#

Generator expressions provide powerful capabilities for conditional property setting:

text
target_compile_definitions(MyApp PRIVATE
"VERSION_STR=\"$<$<CONFIG:Debug>:Debug_>${VERSION}\"")

This adds a different version string definition depending on the build configuration8.

Custom Commands and Build Events#

For special build steps:

text
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generator.py
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/input.txt
)

Configuring Files#

CMake can generate files with variable substitutions:

text
configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)

In config.h.in:

c
#define VERSION "@PROJECT_VERSION@"

This will substitute the @PROJECT_VERSION@ token with the actual project version17.

Modern CMake Best Practices#

The field of CMake best practices has evolved over time. Here are the most important guidelines:

  1. Use at least CMake 3.0: Modern CMake features are only available in newer versions8.
  2. Treat CMake code as production code: Keep it clean, organized, and follow consistent patterns8.
  3. Define project properties at target level: Avoid global settings that affect all targets8.
  4. Forget directory-level commands: Avoid add_compile_options, include_directories, link_directories, and link_libraries8.
  5. Keep your hands off CMAKE_CXX_FLAGS: Use target properties instead8.
  6. Always specify visibility with target commands: Use PRIVATE, PUBLIC, or INTERFACE appropriately8.
  7. Don’t use file(GLOB) for source files: List source files explicitly so CMake can detect when they change8.
  8. Create proper installation rules: Define what gets installed and where8.

Multi-Project Organization#

For larger projects with multiple components:

text
# Top-level CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(SuperProject VERSION 1.0)

# Add subdirectories for components
add_subdirectory(lib)
add_subdirectory(app)

In app/CMakeLists.txt:

text
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib)

This approach enables proper dependency tracking between subprojects3.

Package Installation and Export#

To make your library usable by other CMake projects:

text
install(TARGETS MyLib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)

install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)

configure_package_config_file(
MyLibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION lib/cmake/MyLib
)

install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
DESTINATION lib/cmake/MyLib
)

This creates a complete package that other projects can use with find_package(MyLib)8.

Debugging CMake#

For troubleshooting CMake issues:

bash
# Verbose output during build
cmake --build build --verbose

# See variable values
message(STATUS "My variable: ${MY_VARIABLE}")

# Dump all variables
cmake -LA .

# Inspect the build system
cmake --trace-expand .