# 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: ```cmake # 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: ```cmake # 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: ```cmake 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 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.