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:
find_package() for all external dependenciesEigen3::Eigen3), not raw variablestarget_sources() to add sources to targetsset(CMAKE_...) commands (except at project start for consistent settings)target_compile_features() to specify C++ features instead of flagsExample 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:
Migrate old CMake projects gradually: replace global commands with target-specific ones as you touch files. Modern CMake is worth the effort.