Skip to content

CMake Cheatsheet

Overview

CMake is a cross-platform build system generator that uses platform-independent configuration files to generate native build tool files (Makefiles, Visual Studio projects, Xcode projects, etc.).

Installation

Package Managers

bash
# Ubuntu/Debian
sudo apt install cmake

# macOS
brew install cmake

# CentOS/RHEL
sudo yum install cmake

# Windows (Chocolatey)
choco install cmake

# From source
wget https://cmake.org/files/v3.25/cmake-3.25.0.tar.gz
tar -xzf cmake-3.25.0.tar.gz
cd cmake-3.25.0 && ./bootstrap && make && sudo make install

Basic Concepts

Key Terms

CMakeLists.txt  # Configuration file
Target          # Executable, library, or custom target
Generator       # Tool that creates build files
Cache           # Stored configuration variables
Out-of-source   # Build directory separate from source

Project Structure

project/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   └── utils.cpp
├── include/
│   └── utils.h
└── build/          # Out-of-source build directory

Basic CMakeLists.txt

Minimal Example

cmake
cmake_minimum_required(VERSION 3.10)
project(MyProject)

set(CMAKE_CXX_STANDARD 17)

add_executable(myapp main.cpp)

Simple Library

cmake
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Add library
add_library(mylib utils.cpp network.cpp)

# Add executable
add_executable(myapp main.cpp)

# Link library to executable
target_link_libraries(myapp mylib)

Variables

Built-in Variables

cmake
# Project information
${PROJECT_NAME}
${PROJECT_VERSION}
${PROJECT_SOURCE_DIR}
${PROJECT_BINARY_DIR}

# System information
${CMAKE_SYSTEM_NAME}        # Linux, Windows, Darwin
${CMAKE_SYSTEM_VERSION}
${CMAKE_SYSTEM_PROCESSOR}

# Compiler information
${CMAKE_CXX_COMPILER}
${CMAKE_CXX_COMPILER_ID}    # GNU, Clang, MSVC
${CMAKE_CXX_COMPILER_VERSION}

# Build information
${CMAKE_BUILD_TYPE}         # Debug, Release, RelWithDebInfo
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}

Setting Variables

cmake
# Set variable
set(SOURCES main.cpp utils.cpp)
set(MY_VAR "value")

# List operations
list(APPEND SOURCES network.cpp)
list(REMOVE_ITEM SOURCES main.cpp)
list(LENGTH SOURCES NUM_SOURCES)

# String operations
string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER)
string(REPLACE "old" "new" NEW_STRING ${OLD_STRING})

# Cache variables
set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries")

Targets

Executables

cmake
# Simple executable
add_executable(myapp main.cpp)

# Executable with multiple sources
add_executable(myapp 
    main.cpp
    utils.cpp
    network.cpp
)

# Executable with variable sources
set(SOURCES main.cpp utils.cpp)
add_executable(myapp ${SOURCES})

Libraries

cmake
# Static library
add_library(mylib STATIC utils.cpp network.cpp)

# Shared library
add_library(mylib SHARED utils.cpp network.cpp)

# Header-only library
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include/)

# Object library
add_library(mylib OBJECT utils.cpp network.cpp)
add_executable(myapp main.cpp $<TARGET_OBJECTS:mylib>)

Target Properties

cmake
# Set target properties
set_target_properties(myapp PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    OUTPUT_NAME "my_application"
)

# Include directories
target_include_directories(myapp 
    PRIVATE include/
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/public_include/
)

# Compile definitions
target_compile_definitions(myapp 
    PRIVATE DEBUG_MODE
    PUBLIC API_VERSION=2
)

# Compile options
target_compile_options(myapp 
    PRIVATE -Wall -Wextra
    PUBLIC -fPIC
)

# Link libraries
target_link_libraries(myapp 
    PRIVATE mylib
    PUBLIC pthread
)

Finding Packages

find_package

cmake
# Find required package
find_package(Threads REQUIRED)
target_link_libraries(myapp Threads::Threads)

# Find optional package
find_package(OpenSSL)
if(OpenSSL_FOUND)
    target_link_libraries(myapp OpenSSL::SSL OpenSSL::Crypto)
    target_compile_definitions(myapp PRIVATE HAVE_OPENSSL)
endif()

# Find package with components
find_package(Boost REQUIRED COMPONENTS system filesystem)
target_link_libraries(myapp Boost::system Boost::filesystem)

# Find package with version
find_package(Qt5 5.10 REQUIRED COMPONENTS Core Widgets)
target_link_libraries(myapp Qt5::Core Qt5::Widgets)

pkg-config

cmake
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)

target_include_directories(myapp PRIVATE ${GTK3_INCLUDE_DIRS})
target_link_libraries(myapp ${GTK3_LIBRARIES})
target_compile_options(myapp PRIVATE ${GTK3_CFLAGS_OTHER})

Conditional Logic

if/else/endif

cmake
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_compile_definitions(myapp PRIVATE DEBUG_MODE)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
    target_compile_definitions(myapp PRIVATE RELEASE_MODE)
endif()

# Platform-specific code
if(WIN32)
    target_sources(myapp PRIVATE windows_specific.cpp)
elseif(UNIX)
    target_sources(myapp PRIVATE unix_specific.cpp)
endif()

# Compiler-specific options
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options(myapp PRIVATE -Wall -Wextra)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    target_compile_options(myapp PRIVATE /W4)
endif()

option

cmake
option(BUILD_TESTS "Build test programs" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

Functions and Macros

Functions

cmake
function(add_my_executable name)
    add_executable(${name} ${ARGN})
    target_compile_features(${name} PRIVATE cxx_std_17)
    target_compile_options(${name} PRIVATE -Wall -Wextra)
endfunction()

# Usage
add_my_executable(myapp main.cpp utils.cpp)

Macros

cmake
macro(set_default_build_type)
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
    endif()
endmacro()

# Usage
set_default_build_type()

Advanced Functions

cmake
function(add_library_with_alias target_name)
    set(options SHARED STATIC)
    set(oneValueArgs ALIAS)
    set(multiValueArgs SOURCES HEADERS)
    
    cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
    
    if(ARG_SHARED)
        add_library(${target_name} SHARED ${ARG_SOURCES})
    else()
        add_library(${target_name} STATIC ${ARG_SOURCES})
    endif()
    
    if(ARG_ALIAS)
        add_library(${ARG_ALIAS} ALIAS ${target_name})
    endif()
endfunction()

# Usage
add_library_with_alias(mylib 
    SHARED 
    ALIAS MyNamespace::mylib
    SOURCES utils.cpp network.cpp
)

Testing

CTest Integration

cmake
enable_testing()

# Add test executable
add_executable(test_utils test_utils.cpp)
target_link_libraries(test_utils mylib)

# Add test
add_test(NAME test_utils COMMAND test_utils)

# Test with arguments
add_test(NAME test_with_args COMMAND myapp --test --verbose)

# Test properties
set_tests_properties(test_utils PROPERTIES
    TIMEOUT 30
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

Google Test Integration

cmake
find_package(GTest REQUIRED)

add_executable(unit_tests
    test_main.cpp
    test_utils.cpp
)

target_link_libraries(unit_tests
    mylib
    GTest::GTest
    GTest::Main
)

# Discover tests automatically
include(GoogleTest)
gtest_discover_tests(unit_tests)

Installation

Basic Installation

cmake
# Install executable
install(TARGETS myapp
    RUNTIME DESTINATION bin
)

# Install library
install(TARGETS mylib
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
)

# Install headers
install(DIRECTORY include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h"
)

# Install files
install(FILES config.txt
    DESTINATION etc
)

Package Configuration

cmake
# Generate package config files
include(CMakePackageConfigHelpers)

# Create config file
configure_package_config_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake
    INSTALL_DESTINATION lib/cmake/MyProject
)

# Create version file
write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

# Install config files
install(FILES
    ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
    DESTINATION lib/cmake/MyProject
)

# Export targets
install(EXPORT MyProjectTargets
    FILE MyProjectTargets.cmake
    NAMESPACE MyProject::
    DESTINATION lib/cmake/MyProject
)

Generator Expressions

Basic Generator Expressions

cmake
# Build type specific
target_compile_definitions(myapp PRIVATE
    $<$<CONFIG:Debug>:DEBUG_MODE>
    $<$<CONFIG:Release>:RELEASE_MODE>
)

# Platform specific
target_compile_definitions(myapp PRIVATE
    $<$<PLATFORM_ID:Windows>:WINDOWS_BUILD>
    $<$<PLATFORM_ID:Linux>:LINUX_BUILD>
)

# Compiler specific
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-Wall>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

Advanced Generator Expressions

cmake
# Target properties
target_include_directories(myapp PRIVATE
    $<TARGET_PROPERTY:mylib,INTERFACE_INCLUDE_DIRECTORIES>
)

# Conditional linking
target_link_libraries(myapp
    $<$<BOOL:${BUILD_SHARED_LIBS}>:${CMAKE_DL_LIBS}>
)

# File operations
target_sources(myapp PRIVATE
    $<$<PLATFORM_ID:Windows>:windows_main.cpp>
    $<$<PLATFORM_ID:Linux>:linux_main.cpp>
)

Command Line Usage

Basic Commands

bash
# Configure (generate build files)
cmake -S . -B build

# Build
cmake --build build

# Install
cmake --install build

# Test
cd build && ctest

# Clean
cmake --build build --target clean

Configuration Options

bash
# Set build type
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# Set variables
cmake -S . -B build -DBUILD_TESTS=ON -DBUILD_SHARED_LIBS=ON

# Set generator
cmake -S . -B build -G "Unix Makefiles"
cmake -S . -B build -G "Ninja"
cmake -S . -B build -G "Visual Studio 16 2019"

# Set toolchain
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake

# Parallel build
cmake --build build --parallel 4
cmake --build build -j 4

Advanced Usage

bash
# Configure with preset
cmake --preset=default

# Build specific target
cmake --build build --target myapp

# Install to custom prefix
cmake --install build --prefix /opt/myapp

# Run specific test
cd build && ctest -R test_utils

# Verbose output
cmake --build build --verbose
ctest --verbose

Cross-Platform Development

Platform Detection

cmake
if(WIN32)
    # Windows-specific code
    target_compile_definitions(myapp PRIVATE PLATFORM_WINDOWS)
elseif(APPLE)
    # macOS-specific code
    target_compile_definitions(myapp PRIVATE PLATFORM_MACOS)
elseif(UNIX)
    # Linux/Unix-specific code
    target_compile_definitions(myapp PRIVATE PLATFORM_LINUX)
endif()

Toolchain Files

cmake
# toolchain-mingw.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# Usage
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=toolchain-mingw.cmake

Best Practices

Project Structure

cmake
cmake_minimum_required(VERSION 3.15)

project(MyProject 
    VERSION 1.0.0
    DESCRIPTION "My awesome project"
    LANGUAGES CXX
)

# Only do these if this is the main project
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set_property(GLOBAL PROPERTY USE_FOLDERS ON)
    
    include(CTest)
    if(BUILD_TESTING)
        add_subdirectory(tests)
    endif()
endif()

# Add subdirectories
add_subdirectory(src)
add_subdirectory(external)

Modern CMake Patterns

cmake
# Use target-based approach
target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# Use imported targets
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE Threads::Threads)

# Use generator expressions
target_compile_features(myapp PRIVATE cxx_std_17)

# Avoid global commands
# Don't use: include_directories(), link_directories(), add_definitions()
# Use target-specific commands instead

Error Handling

cmake
# Check CMake version
cmake_minimum_required(VERSION 3.15)

# Validate variables
if(NOT DEFINED PROJECT_VERSION)
    message(FATAL_ERROR "PROJECT_VERSION must be defined")
endif()

# Check for required files
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp")
    message(FATAL_ERROR "main.cpp not found")
endif()

# Validate options
if(BUILD_TYPE AND NOT BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)$")
    message(FATAL_ERROR "Invalid BUILD_TYPE: ${BUILD_TYPE}")
endif()

Debugging

Debug Output

cmake
# Print variables
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
message(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}")

# Print all variables
get_cmake_property(_variableNames VARIABLES)
foreach(_variableName ${_variableNames})
    message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

# Print target properties
get_target_property(MYAPP_SOURCES myapp SOURCES)
message(STATUS "myapp sources: ${MYAPP_SOURCES}")

Troubleshooting

bash
# Verbose makefile
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON

# Debug find_package
cmake -S . -B build --debug-find

# Trace execution
cmake -S . -B build --trace

# Debug output
cmake -S . -B build --debug-output

Resources