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
