Appearance
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
- Official Documentation: cmake.org/documentation
- CMake Tutorial: cmake.org/cmake/help/latest/guide/tutorial
- Modern CMake: cliutils.gitlab.io/modern-cmake
- CMake Examples: github.com/ttroy50/cmake-examples