Site Tools


wiki:cmake-modern-practices

Table of Contents

CMake Modern Practices

Modern CMake (3.12+) is target-based: apply settings to individual targets with target_* commands, not globally. This produces cleaner, more maintainable builds and avoids side effects.

Target-based approach:

# GOOD: target-specific settings
add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE include/)
target_compile_options(myapp PRIVATE -Wall -Wextra)
target_link_libraries(myapp PRIVATE mylib)
 
# BAD: global settings (affects all targets)
include_directories(include/)
add_compile_options(-Wall -Wextra)
link_libraries(mylib)

Target-specific compilation flags:

# GOOD: per-target
add_library(mylib lib.cpp)
target_compile_options(mylib PRIVATE -O3 -march=native)
 
add_executable(debug_tool tool.cpp)
target_compile_options(debug_tool PRIVATE -g -DDEBUG)
 
# BAD: global (all targets get the same flags)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native")

Use PUBLIC/PRIVATE/INTERFACE to control visibility:

add_library(mylib lib.cpp)
target_include_directories(mylib PUBLIC include/)  # consumers need headers
target_compile_definitions(mylib PRIVATE DEBUG=1)  # only for compilation
 
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)

Modern conventions:

  • Use find_package() for all external dependencies
  • Link to package targets (e.g., Eigen3::Eigen3), not raw variables
  • Use target_sources() to add sources to targets
  • Avoid global set(CMAKE_...) commands (except at project start for consistent settings)
  • Use target_compile_features() to specify C++ features instead of flags

Example modern project:

cmake_minimum_required(VERSION 3.20)
project(MyApp CXX)
 
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
find_package(Eigen3 3.4 REQUIRED)
 
add_library(mylib lib.cpp)
target_include_directories(mylib PUBLIC include/)
target_link_libraries(mylib PUBLIC Eigen3::Eigen3)
target_compile_features(mylib PUBLIC cxx_std_17)
 
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
 
enable_testing()
add_executable(test_app test.cpp)
add_test(NAME Tests COMMAND test_app)
target_link_libraries(test_app PRIVATE mylib)

Benefits of modern CMake:

  • Predictable builds — each target explicitly declares dependencies
  • Modularity — subdirectories don't pollute global state
  • Reusability — generated CMake config files work for consumers
  • IDE integration — modern generators understand target structure better
  • Maintainability — clear what each target needs

Migrate old CMake projects gradually: replace global commands with target-specific ones as you touch files. Modern CMake is worth the effort.

wiki/cmake-modern-practices.md · Last modified: by 127.0.0.1