agentleFS
ROCmROCm/rocprofiler-systems-skills/skills/programming-cmake-best-practices/SKILL.md

programming-cmake-best-practices

Skill5 starsChanged 16 days agoMITView on GitHub

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

  • Deletes or force-pushes

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 2 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Think in terms of targets, not variables: - Use target_*() commands instead of global variables

    line 12
  2. Distinctive:

    Avoid global scope pollution: - Keep settings attached to specific targets

    line 14
  3. Distinctive:

    PRIVATE: Only this target needs it

    line 78
  4. Very specific:

    Modularize with subdirectories: - Use add_subdirectory() for organized structure

    line 172
  5. Very specific:

    Always use cmake_minimum_required() at the top

    line 184
  6. Very specific:

    Never use file(GLOB) for source files in production - list them explicitly

    line 185
  7. Very specific:

    Use target_sources() to add sources after target creation

    line 186
  8. Very specific:

    Prefer option() for user-configurable settings

    line 187
  9. Very specific:

    Use CMAKE_PROJECT_NAME instead of hardcoded project names

    line 188
  10. Very specific:

    Use cmake_path() (CMake 3.20+) for path manipulation

    line 190

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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_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):

include_directories(${PROJECT_SOURCE_DIR}/include)
add_definitions(-DMY_DEFINE)
link_directories(${SOME_LIB_DIR})

NEW (use this):

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
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

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+)

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)

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

# 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)

target_compile_options(myapp PRIVATE
    $<$:-Wall -Wextra -pedantic>
    $<$:/W4>
)

Export and Install Targets

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

# 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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.