CMake Cheatsheet
Sinopsis
CMake es un generador de sistemas de construcción multiplataforma que utiliza archivos de configuración independientes de plataforma para generar archivos de herramientas de construcción nativa (Makefiles, proyectos de Visual Studio, proyectos Xcode, etc.).
Instalación
Administradores de paquetes
# 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
Conceptos básicos
Términos clave
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
Estructura del proyecto
project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── utils.cpp
├── include/
│ └── utils.h
└── build/ # Out-of-source build directory
Basic CMakeLists.txt
Ejemplo mínimo
cmake_minimum_required(VERSION 3.10)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
add_executable(myapp main.cpp)
Biblioteca simple
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
Variables incorporadas
# 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\\\\}
Ajuste de variables
# 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")
Metas
Ejecutables
# 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\\\\})
Bibliotecas
# 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>)
Propiedades de destino
# 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
)
Encontrar paquetes
encontrar_paquete
# 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
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\\\\})
Lógica condicional
if/else/endif
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()
Opción
option(BUILD_TESTS "Build test programs" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
Funciones y macros
Funciones
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
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()
Funciones avanzadas
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
)
Pruebas
CTest Integration
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
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)
Instalación
Instalación básica
# 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
)
Configuración del paquete
# 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
)
Expresiones del Generador
Expresiones de generadores básicos
# 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>
)
Expresiones de generador avanzado
# 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>
)
Uso de la línea de comandos
Comandos básicos
# 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
Opciones de configuración
# 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
Uso avanzado
# 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
Detección de plataformas
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()
Archivo de la cadena de herramientas
# 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
Buenas prácticas
Estructura del proyecto
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)
Patrones CMake modernos
# 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
Manejo de errores
# 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
# 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\\\\}")
Solución de problemas
# 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
Recursos
- ** Documentación oficial**: cmake.org/documentation
- CMake Tutorial: cmake.org/cmake/help/latest/guide/tutorial
- Modern CMake: cliutils.gitlab.io/modern-cmake
- CMake Ejemplos: github.com/ttroy50/cmake-examples