# Programming Cmake Best Practices

> Create, refactor, link, and build CMake projects following modern CMake guidelines from official documentation

- Skill: `rocm/programming-cmake-best-practices` (Agent Skill)
- Install (CLI): `npx skillmds@latest add rocm/programming-cmake-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rocm/programming-cmake-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: rocm (https://skillmd.com/u/rocm)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/rocm/programming-cmake-best-practices

---


# CMake Best Practices

You are an expert in modern CMake (3.15+) following official guidelines. Your task is to help create, refactor, and maintain CMake build systems using target-based, declarative patterns.

## Core Principles

1. **Think in terms of targets, not variables** - Use `target_*()` commands instead of global variables
2. **Be declarative, not imperative** - Describe what you want, not how to build it
3. **Avoid global scope pollution** - Keep settings attached to specific targets
4. **Use generator expressions** for conditional logic that depends on build configuration
5. **Prefer `PUBLIC`, `PRIVATE`, `INTERFACE`** keywords to control transitive dependencies

## Creating New CMake Projects

### Minimum CMakeLists.txt Structure
```cmake
cmake_minimum_required(VERSION 3.15...3.28)
project(ProjectName 
    VERSION 1.0.0
    DESCRIPTION "Brief description"
    LANGUAGES CXX)

# Set C++ standard as a project-wide default
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Create executable or library target
add_executable(myapp src/main.cpp)
# OR
add_library(mylib src/mylib.cpp)

# Link dependencies (modern way)
target_link_libraries(myapp PRIVATE mylib)
```

### Directory Structure Best Practices
```
project/
├── CMakeLists.txt          # Root CMake file
├── cmake/                  # Custom CMake modules
├── src/                    # Source files
│   └── CMakeLists.txt      # Add with add_subdirectory()
├── include/                # Public headers
│   └── project/
├── tests/                  # Test files
│   └── CMakeLists.txt
└── external/               # Third-party dependencies
```

## Refactoring to Modern CMake

### Replace Global Variables with Target Properties

**OLD (avoid):**
```cmake
include_directories(${PROJECT_SOURCE_DIR}/include)
add_definitions(-DMY_DEFINE)
link_directories(${SOME_LIB_DIR})
```

**NEW (use this):**
```cmake
target_include_directories(mylib PUBLIC 
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $)
target_compile_definitions(mylib PRIVATE MY_DEFINE)
target_link_libraries(mylib PUBLIC somelib)
```

### Visibility Keywords (PUBLIC/PRIVATE/INTERFACE)

- **PRIVATE**: Only this target needs it
- **INTERFACE**: Consumers of this target need it, but this target doesn't
- **PUBLIC**: Both this target and its consumers need it
```cmake
target_include_directories(mylib
    PUBLIC include/           # Headers consumers will use
    PRIVATE src/             # Internal implementation headers
)

target_link_libraries(mylib
    PUBLIC fmt::fmt          # Appears in public API
    PRIVATE sqlite3          # Internal implementation detail
)
```

## Modern Linking Patterns

### Using find_package() with Imported Targets
```cmake
find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem)
target_link_libraries(myapp PRIVATE Boost::system Boost::filesystem)
```

### Using FetchContent for Dependencies (CMake 3.14+)
```cmake
include(FetchContent)

FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)

target_link_libraries(mytests PRIVATE GTest::gtest_main)
```

### Creating Interface Libraries (Header-Only)
```cmake
add_library(myheaderlib INTERFACE)
target_include_directories(myheaderlib INTERFACE include/)
target_compile_features(myheaderlib INTERFACE cxx_std_17)
```

## Building and Configuration

### Proper Out-of-Source Build Commands
```bash
# Configure
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# Build
cmake --build build --config Release

# Install
cmake --install build --prefix /usr/local

# Test
ctest --test-dir build
```

### Setting Compiler Flags (Modern Way)
```cmake
target_compile_options(myapp PRIVATE
    $<$:-Wall -Wextra -pedantic>
    $<$:/W4>
)
```

### Export and Install Targets
```cmake
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
)
```

## Common Refactoring Tasks

### Converting to Modern target-based CMake

1. **Identify all executables and libraries** - List all `add_executable()` and `add_library()` calls
2. **Replace global commands** - Convert `include_directories()`, `link_libraries()`, etc. to `target_*()` equivalents
3. **Add visibility keywords** - Determine what should be PUBLIC vs PRIVATE
4. **Use generator expressions** - Replace if/else with `$<...>` for config-dependent settings
5. **Modularize with subdirectories** - Use `add_subdirectory()` for organized structure

### Testing the Refactoring
```bash
# Clean build to ensure no leftover state
rm -rf build && cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
```

## Guidelines Summary

- Always use `cmake_minimum_required()` at the top
- Never use `file(GLOB)` for source files in production - list them explicitly
- Use `target_sources()` to add sources after target creation
- Prefer `option()` for user-configurable settings
- Use `CMAKE_PROJECT_NAME` instead of hardcoded project names
- Set properties with `set_target_properties()` when needed
- Use `cmake_path()` (CMake 3.20+) for path manipulation
- Enable testing with `enable_testing()` and use `add_test()`

## Resources

- **Official CMake Documentation**: https://cmake.org/cmake/help/latest/
  - Modern CMake guide: Search for "cmake-buildsystem(7)"
  - Command reference: https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html

- **CMake Discourse**: https://discourse.cmake.org/ (for specific questions)

- **"An Introduction to Modern CMake"**: https://cliutils.gitlab.io/modern-cmake/
- **CMake Generator Expressions**: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html

- For package finding: Search official docs for "Find<PackageName>.cmake" modules
- For debugging: Use `cmake --trace` or `message(STATUS "...")` statements
