Saltar a contenido

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