Руководство по основным командам CMake

Автор: | 8 февраля, 2026

Содержание

Что такое CMake

CMake — это мета-система сборки (build system generator), которая не компилирует код напрямую, а генерирует файлы для нативных систем сборки (Makefile, Visual Studio проекты, Xcode проекты и т.д.). Основная философия CMake — написать один раз, собирать везде.

CMake использует декларативный язык, где вы описываете что нужно собрать, а не как это делать. Это делает скрипты кроссплатформенными и легко поддерживаемыми.

Базовые команды проекта

cmake_minimum_required()

Назначение: Указывает минимальную версию CMake.

# Всегда должна быть первой командой в CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# Или с политикой FATAL_ERROR
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)

Почему это важно: Разные версии CMake имеют разный синтаксис и возможности. Указание версии гарантирует совместимость.

 project()

Назначение: Определяет проект и его свойства.

# Базовая форма
project(MyProject)

# С дополнительными параметрами
project(
    MyAwesomeApp
    VERSION 1.2.3          # Задает версию проекта
    DESCRIPTION "My application"
    LANGUAGES C CXX        # Языки проекта (C, CXX, Fortran, etc.)
    HOMEPAGE_URL "https://example.com"
)

# Переменные, создаваемые командой project():
# ${PROJECT_NAME}           # Имя проекта
# ${PROJECT_VERSION}        # Полная версия
# ${PROJECT_VERSION_MAJOR}  # Старшая версия
# ${PROJECT_VERSION_MINOR}  # Младшая версия
# ${PROJECT_VERSION_PATCH}  # Патч-версия
# ${PROJECT_SOURCE_DIR}     # Путь к исходникам
# ${PROJECT_BINARY_DIR}     # Путь к папке сборки

add_subdirectory()

Назначение: Добавляет поддиректорию с собственным CMakeLists.txt.

# Простая форма
add_subdirectory(src)

# С указанием бинарной директории
add_subdirectory(external/libpng bin/libpng)

# Условное добавление
if(USE_TESTS)
    add_subdirectory(tests)
endif()

Лучшие практики: Разбивайте большие проекты на поддиректории с отдельными CMakeLists.txt.

Команды для создания таргетов

 add_executable()

Назначение: Создает исполняемый файл.

# Простая форма
add_executable(myapp main.cpp)

# С несколькими файлами
add_executable(myapp 
    main.cpp
    utils.cpp
    gui/mainwindow.cpp
)

# Использование переменных для списка файлов
set(SOURCES 
    src/main.cpp
    src/core.cpp
    src/gui.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})

# Интерфейсная библиотека (CMake 3.19+)
add_executable(myapp_exe)
target_sources(myapp_exe PRIVATE main.cpp)

 add_library()

Назначение: Создает библиотеку.

# Статическая библиотека (.a, .lib)
add_library(mylib STATIC 
    lib.cpp
    lib.hpp
)

# Динамическая/разделяемая библиотека (.so, .dll, .dylib)
add_library(mylib SHARED 
    lib.cpp
    lib.hpp
)

# Объектная библиотека (только объектные файлы)
add_library(myobjects OBJECT obj1.cpp obj2.cpp)

# Интерфейсная библиотека (без исходного кода)
add_library(mylib_interface INTERFACE)

# Импортированная библиотека (внешняя)
add_library(external_lib STATIC IMPORTED)
set_target_properties(external_lib PROPERTIES
    IMPORTED_LOCATION "/path/to/libexternal.a"
)

# Библиотека псевдоним
add_library(mylib SHARED lib.cpp)
add_library(mylib::mylib ALIAS mylib)  # Для современного использования

Типы библиотек и их применение

ТипФайлыИспользование
STATIC.a (Linux), .lib (Windows)Включается в исполняемый файл
SHARED.so.dll.dylibЗагружается во время выполнения
OBJECT.o.objПромежуточные объектные файлы
INTERFACEНет файловТолько свойства и зависимости
MODULE.so.dllПлагины, загружаемые во время выполнения

target_sources()

Назначение: Добавляет исходные файлы к существующему таргету.

add_executable(myapp)  # Сначала создаем пустой таргет
target_sources(myapp PRIVATE
    src/main.cpp
    src/utils.cpp
)

# Можно добавлять файлы условно
if(WIN32)
    target_sources(myapp PRIVATE src/win32.cpp)
else()
    target_sources(myapp PRIVATE src/posix.cpp)
endif()

Ключевые слова:

  • PRIVATE — только для этого таргета
  • PUBLIC — для этого таргета и тех, кто его использует
  • INTERFACE — только для тех, кто использует этот таргет

Команды управления файлами и директориями

 include_directories() (устаревший, но часто встречается)

Назначение: Добавляет директории для поиска заголовочных файлов.

# Глобальное добавление (влияет на все таргеты)
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${PROJECT_SOURCE_DIR}/src
)

# Предпочтительнее использовать:
target_include_directories(mylib PUBLIC include)

 target_include_directories() (современный подход)

Назначение: Добавляет пути для заголовочных файлов для конкретного таргета.

add_library(mylib lib.cpp)

# PUBLIC - заголовки нужны и пользователям библиотеки
target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# PRIVATE - только для сборки библиотеки
target_include_directories(mylib PRIVATE src)

# INTERFACE - только для пользователей библиотеки
target_include_directories(mylib_interface INTERFACE include)

# Пример с генератором выражений
target_include_directories(mylib SYSTEM PUBLIC
    $<INSTALL_PREFIX>/include
)

 link_directories() (стараться избегать)

# Не рекомендуется
link_directories(/usr/local/lib /opt/libs)

# Вместо этого используйте:
find_library(MY_LIB mylib PATHS /usr/local/lib)
target_link_libraries(myapp ${MY_LIB})

target_link_directories() (CMake 3.13+)

target_link_directories(myapp
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libs>
        $<INSTALL_INTERFACE:lib>
)

 target_link_libraries()

Назначение: Связывает таргеты между собой.

# Простая форма
target_link_libraries(myapp mylib)

# С несколькими библиотеками
target_link_libraries(myapp
    PRIVATE
        mylib1
        mylib2::mylib2  # Именованный таргет
        ${CMAKE_THREAD_LIBS_INIT}
    PUBLIC
        common_lib      # Передается потребителям
    INTERFACE
        header_only_lib # Только интерфейс
)

# С дополнительными флагами линковки (старый стиль)
target_link_libraries(myapp 
    -Wl,--as-needed
    -lpthread
    mylib
)

Команды для работы с файлами

# Поиск файлов
file(GLOB SOURCE_FILES "src/*.cpp")
file(GLOB_RECURSE HEADER_FILES "include/*.h" "src/*.hpp")

# Копирование файлов
configure_file(config.h.in config.h)
file(COPY resources DESTINATION ${CMAKE_BINARY_DIR})

# Чтение/запись файлов
file(READ "${PROJECT_SOURCE_DIR}/VERSION" PROJECT_VERSION)
file(WRITE "${CMAKE_BINARY_DIR}/info.txt" "Built: ${CMAKE_BUILD_TYPE}")

# Операции с файлами
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)
file(REMOVE ${CMAKE_BINARY_DIR}/temp.txt)

# Генерация файлов
file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/output.txt
     CONTENT "Project: ${PROJECT_NAME}\nVersion: ${PROJECT_VERSION}")

Команды настройки компиляции

Флаги компиляции

# Глобальные флаги (не рекомендуется)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")

# Для конкретного таргета (предпочтительно)
target_compile_options(myapp PRIVATE
    -Wall
    -Wextra
    -Werror
    $<$<CXX_COMPILER_ID:MSVC>:/W4>  # Для MSVC
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wpedantic>
)

# Флаги для разных конфигураций
target_compile_options(myapp
    PRIVATE
        $<$<CONFIG:Debug>:-O0 -g>
        $<$<CONFIG:Release>:-O3 -DNDEBUG>
        $<$<CONFIG:RelWithDebInfo>:-O2 -g>
)

# Определения препроцессора
target_compile_definitions(myapp
    PRIVATE
        VERSION=\"${PROJECT_VERSION}\"
        HAVE_CONFIG_H
    PUBLIC
        MYLIB_EXPORTS  # Для DLL
    INTERFACE
        MYLIB_HEADER_ONLY
)

Настройка стандарта языка

# Старый стиль
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Для конкретного таргета (CMake 3.8+)
target_compile_features(myapp
    PUBLIC
        cxx_std_17      # Требует C++17
    PRIVATE
        cxx_constexpr   # Конкретные фичи
        cxx_auto_type
)

# Разные стандарты для разных таргетов
set_target_properties(app1 PROPERTIES
    CXX_STANDARD 14
    CXX_STANDARD_REQUIRED ON
)

set_target_properties(app2 PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
)

Настройка свойств таргетов

set_target_properties(myapp PROPERTIES
    # Имя выходного файла
    OUTPUT_NAME "myapplication"
    PREFIX ""  # Убрать префикс
    SUFFIX ""  # Убрать суффикс
    
    # Версия библиотеки
    VERSION 1.2.3
    SOVERSION 1
    
    # Позиционно-независимый код
    POSITION_INDEPENDENT_CODE ON
    
    # Флаги линковки
    LINK_FLAGS "-Wl,--export-dynamic"
    LINKER_LANGUAGE CXX
    
    # Инсталляционные пути
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
    ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
    
    # Особенности компилятора
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
    
    # Windows специфика
    WIN32_EXECUTABLE ON  # Приложение Windows (не консольное)
    MACOSX_BUNDLE ON     # Приложение macOS
)

Автоматические обработчики (для Qt, ресурсов и т.д.)

# Автоматический запуск MOC для Qt
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

# Параллельная обработка
set(CMAKE_AUTOMOC_PARALLEL ON)

# Автоматическая обработка ресурсов
set_source_files_properties(resources.qrc PROPERTIES
    AUTORCC ON
)

Команды для поиска зависимостей

 find_package()

# Базовая форма
find_package(OpenSSL REQUIRED)

# С компонентами
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network)

# С опциями
find_package(Boost 1.70 REQUIRED
    COMPONENTS filesystem system thread
    OPTIONAL_COMPONENTS python
)

# Разные режимы поиска
find_package(OpenCV MODULE)      # Искать FindOpenCV.cmake
find_package(OpenCV CONFIG)      # Искать OpenCVConfig.cmake

# Переменные после find_package():
# <Package>_FOUND           - Найден ли пакет
# <Package>_INCLUDE_DIRS    - Директории заголовков
# <Package>_LIBRARIES       - Библиотеки
# <Package>_VERSION         - Версия пакета

if(OpenSSL_FOUND)
    message(STATUS "OpenSSL found: ${OpenSSL_VERSION}")
    target_link_libraries(myapp OpenSSL::SSL OpenSSL::Crypto)
endif()

 find_library()find_path()find_program()

# Поиск библиотеки
find_library(ZLIB_LIBRARY
    NAMES z zlib zlib1
    PATHS /usr/local/lib /opt/lib
    PATH_SUFFIXES lib lib64
    REQUIRED
)

# Поиск пути к заголовкам
find_path(ZLIB_INCLUDE_DIR
    NAMES zlib.h
    PATHS /usr/local/include /opt/include
    DOC "Path to zlib headers"
)

# Поиск исполняемого файла
find_program(PYTHON_EXECUTABLE
    NAMES python3 python
    PATHS /usr/bin /usr/local/bin
    REQUIRED
)

# Использование результатов
if(ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR)
    add_library(zlib STATIC IMPORTED)
    set_target_properties(zlib PROPERTIES
        IMPORTED_LOCATION ${ZLIB_LIBRARY}
        INTERFACE_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIR}
    )
endif()

 pkg_check_modules() (для pkg-config)

find_package(PkgConfig REQUIRED)

# Поиск пакета
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)

# Использование
target_include_directories(myapp PUBLIC ${GTK3_INCLUDE_DIRS})
target_link_libraries(myapp ${GTK3_LIBRARIES})
target_compile_options(myapp PRIVATE ${GTK3_CFLAGS_OTHER})

# Статическое связывание
pkg_check_modules(GTK3_STATIC gtk+-3.0 IMPORTED_TARGET STATIC)
if(GTK3_STATIC_FOUND)
    target_link_libraries(myapp PkgConfig::GTK3_STATIC)
endif()

Команды управления переменными

 set()

# Простая переменная
set(MY_VARIABLE "Hello World")

# Список
set(SOURCE_FILES main.cpp utils.cpp gui.cpp)
# или
set(SOURCE_FILES "main.cpp;utils.cpp;gui.cpp")

# Кэшированная переменная (видна в cmake-gui)
set(USE_FEATURE_X ON CACHE BOOL "Enable feature X")

# Переменная окружения
set(ENV{PATH} "$ENV{PATH}:${CMAKE_BINARY_DIR}/bin")

# Свойства глобальные
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type")

# Разные типы кэшированных переменных
set(MY_STRING "default" CACHE STRING "String variable")
set(MY_PATH "/usr/local" CACHE PATH "Path variable")
set(MY_FILE "/etc/config.conf" CACHE FILEPATH "File path")
set(MY_LIST "a;b;c" CACHE STRING "List variable")

# Сброс кэшированных переменных
unset(MY_VARIABLE CACHE)

 list() — операции со списками

# Создание списка
list(APPEND SOURCES main.cpp utils.cpp)
list(APPEND INCLUDES include src)

# Удаление элементов
list(REMOVE_ITEM SOURCES deprecated.cpp)
list(REMOVE_DUPLICATES INCLUDES)

# Поиск в списке
list(FIND SOURCES "main.cpp" INDEX)
if(INDEX EQUAL -1)
    message(WARNING "main.cpp not found!")
endif()

# Сортировка
list(SORT SOURCES)

# Получение элементов
list(GET SOURCES 0 FIRST_FILE)      # Первый элемент
list(LENGTH SOURCES NUM_SOURCES)    # Длина списка

# Фильтрация
list(FILTER SOURCES INCLUDE REGEX "\\.cpp$")
list(FILTER SOURCES EXCLUDE REGEX "test_")

# Преобразование в строку
list(JOIN SOURCES ", " SOURCES_STR)

# Подсписок
list(SUBLIST SOURCES 0 3 FIRST_THREE)  # Элементы 0,1,2

# Обход списка
foreach(FILE ${SOURCES})
    message(STATUS "File: ${FILE}")
endforeach()

 option() — пользовательские опции

# Булевы опции
option(BUILD_TESTS "Build tests" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(ENABLE_ASAN "Enable Address Sanitizer" OFF)

# Использование
if(BUILD_TESTS)
    add_subdirectory(tests)
endif()

# Зависимые опции
option(USE_OPENGL "Use OpenGL" ON)
if(USE_OPENGL)
    option(USE_OPENGL_ES "Use OpenGL ES" OFF)
endif()

 string() — операции со строками

# Преобразование регистра
string(TOLOWER "${PROJECT_NAME}" PROJECT_NAME_LOWER)
string(TOUPPER "${PROJECT_NAME}" PROJECT_NAME_UPPER)

# Поиск и замена
string(FIND "${PATH}" "src" INDEX)
string(REPLACE "old" "new" NEW_STRING "${OLD_STRING}")

# Регулярные выражения
string(REGEX MATCH "[0-9]+" VERSION_NUMBER "${FULL_VERSION}")
string(REGEX REPLACE "^v([0-9.]+)$" "\\1" CLEAN_VERSION "${TAG}")

# Подстроки
string(SUBSTRING "${FULL_STRING}" 0 5 PREFIX)

# Длина
string(LENGTH "${MY_STRING}" STR_LEN)

# Конкатенация
string(CONCAT FULL_NAME ${FIRST} " " ${LAST})

# Разделение и соединение
string(JOIN ":" PATH_PARTS ${PATH_LIST})
string(REPLACE ":" ";" PATH_LIST "${PATH_STRING}")

# Удаление пробелов
string(STRIP "${STRING_WITH_SPACES}" CLEAN_STRING)

# Генерация случайной строки
string(RANDOM LENGTH 10 ALPHABET "ABCDEF0123456789" RANDOM_ID)

 math() — математические операции

# Основные операции
math(EXPR RESULT "1 + 2 * 3")           # RESULT = 7
math(EXPR VERSION_CODE "${MAJOR} << 16 | ${MINOR} << 8 | ${PATCH}")

# Проверка выражений
math(EXPR IS_64BIT "8 * 8")
if(IS_64BIT EQUAL 64)
    set(ARCH "x64")
endif()

# Генерация последовательности
set(NUMBERS "")
foreach(I RANGE 1 10)
    math(EXPR SQUARE "${I} * ${I}")
    list(APPEND NUMBERS ${SQUARE})
endforeach()

Условные операторы и циклы

Условные операторы

# Базовый if
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    message(STATUS "Building for Linux")
endif()

# Полный синтаксис
if(condition1)
    # code...
elseif(condition2)
    # code...
else()
    # code...
endif()

# Логические операторы
if(NOT DISABLE_FEATURE AND (PLATFORM_WIN32 OR PLATFORM_MAC))
    # code...
endif()

# Проверка существования
if(DEFINED MY_VARIABLE)
    # переменная определена
endif()

if(EXISTS ${FILE_PATH})
    # файл существует
endif()

if(IS_DIRECTORY ${DIR_PATH})
    # это директория
endif()

if(COMMAND some_function)
    # команда/функция существует
endif()

# Сравнение версий
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.14")
    # используем новые возможности
endif()

Циклы

# foreach по списку
foreach(SOURCE_FILE ${SOURCE_FILES})
    message(STATUS "Processing: ${SOURCE_FILE}")
    # Можно использовать break() или continue()
    if(${SOURCE_FILE} MATCHES "test_")
        continue()
    endif()
endforeach()

# foreach по диапазону
foreach(I RANGE 1 10 2)  # от 1 до 10 с шагом 2
    message(STATUS "Number: ${I}")
endforeach()

# foreach с ZIP (CMake 3.17+)
foreach(HEADER SOURCE IN ZIP_LISTS HEADERS SOURCES)
    message(STATUS "${HEADER} -> ${SOURCE}")
endforeach()

# while цикл
set(COUNTER 0)
while(COUNTER LESS 10)
    math(EXPR COUNTER "${COUNTER} + 1")
    message(STATUS "Counter: ${COUNTER}")
endwhile()

Генераторные выражения

# Условные выражения для времени генерации
target_compile_options(myapp PRIVATE
    $<$<CONFIG:Debug>:-O0 -g3>
    $<$<CONFIG:Release>:-O3 -DNDEBUG>
)

# Проверка компилятора
target_compile_definitions(myapp PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
    $<$<CXX_COMPILER_ID:GNU>:_GNU_SOURCE>
)

# Проверка платформы
target_link_libraries(myapp PRIVATE
    $<$<PLATFORM_ID:Windows>:winmm>
    $<$<PLATFORM_ID:Linux>:pthread>
)

# Комбинации условий
target_compile_options(myapp PRIVATE
    $<$<AND:$<CONFIG:Debug>,$<CXX_COMPILER_ID:GNU>>:-fsanitize=address>
)

# Получение свойств таргета
add_custom_command(OUTPUT generated.h
    COMMAND generator
    $<TARGET_FILE:myapp>
    $<TARGET_PROPERTY:myapp,OUTPUT_NAME>
)

# Распространенные генераторные выражения:
# $<CONFIG:cfg>          - true для конфигурации cfg
# $<PLATFORM_ID:pid>     - true для платформы pid
# $<CXX_COMPILER_ID:id>  - true для компилятора id
# $<BOOL:value>          - преобразование в bool
# $<AND:cond1,cond2>     - логическое И
# $<OR:cond1,cond2>      - логическое ИЛИ
# $<NOT:cond>            - логическое НЕ
# $<TARGET_FILE:target>  - полный путь к файлу таргета
# $<TARGET_PROPERTY:target,prop> - свойство таргета

Функции и макросы

function()

# Определение функции
function(print_target_info target_name)
    # Аргументы: ARGC - количество, ARGV - все, ARGN - дополнительные
    message(STATUS "Target: ${target_name}")
    message(STATUS "All args: ${ARGV}")
    message(STATUS "Extra args: ${ARGN}")
    
    # Локальные переменные
    set(LOCAL_VAR "local" PARENT_SCOPE)  # для передачи наружу
    
    # Возврат значения через set PARENT_SCOPE
    set(${target_name}_FOUND TRUE PARENT_SCOPE)
endfunction()

# Использование
print_target_info(myapp SOURCES LIBS)

macro()

# Макросы работают как текстовые подстановки
macro(setup_target target type)
    if(${type} STREQUAL "EXECUTABLE")
        add_executable(${target} ${ARGN})
    elseif(${type} STREQUAL "LIBRARY")
        add_library(${target} ${ARGN})
    endif()
    
    target_compile_features(${target} PRIVATE cxx_std_17)
endmacro()

# Использование
setup_target(myapp EXECUTABLE main.cpp utils.cpp)
setup_target(mylib LIBRARY SHARED lib.cpp)

# Различия function vs macro:
# - В функциях переменные локальны, в макросах - видны снаружи
# - Макросы могут изменять переменные вызывающей области
# - Функции создают новую область видимости

Полезные пользовательские функции

# Функция для добавления тестов
function(add_unit_test test_name)
    add_executable(${test_name} ${ARGN})
    target_link_libraries(${test_name} gtest_main mylib)
    add_test(NAME ${test_name} COMMAND ${test_name})
    
    # Установка свойств теста
    set_tests_properties(${test_name} PROPERTIES
        TIMEOUT 30
        LABELS "unit"
    )
endfunction()

# Функция настройки компилятора
function(configure_compiler_warnings target)
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR 
       CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
        target_compile_options(${target} PRIVATE
            -Wall
            -Wextra
            -Wpedantic
            -Werror
            $<$<CONFIG:Debug>:-Wshadow -Wconversion>
        )
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
        target_compile_options(${target} PRIVATE
            /W4
            /WX
        )
    endif()
endfunction()

# Функция для версионирования
function(generate_version_header)
    # Генерация заголовочного файла с версией
    configure_file(
        ${CMAKE_CURRENT_SOURCE_DIR}/version.h.in
        ${CMAKE_CURRENT_BINARY_DIR}/version.h
    )
    
    # Добавление в таргет
    target_sources(${PROJECT_NAME} PRIVATE
        ${CMAKE_CURRENT_BINARY_DIR}/version.h
    )
    
    target_include_directories(${PROJECT_NAME} PRIVATE
        ${CMAKE_CURRENT_BINARY_DIR}
    )
endfunction()

Продвинутые команды и современные практики

add_custom_command()

# Генерация файлов во время сборки
add_custom_command(
    OUTPUT ${CMAKE_BINARY_DIR}/generated.cpp
    COMMAND python3 ${CMAKE_SOURCE_DIR}/scripts/generate.py
            --output ${CMAKE_BINARY_DIR}/generated.cpp
    DEPENDS ${CMAKE_SOURCE_DIR}/scripts/generate.py
            ${CMAKE_SOURCE_DIR}/data/input.txt
    COMMENT "Generating source file"
    VERBATIM
)

# Пост-обработка (после компиляции)
add_custom_command(TARGET myapp POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy
            $<TARGET_FILE:myapp>
            ${CMAKE_BINARY_DIR}/dist/
    COMMENT "Copying executable to dist folder"
)

# Предобработка (перед линковкой)
add_custom_command(TARGET mylib PRE_LINK
    COMMAND objcopy --add-section .custom=data.bin mylib.o
    COMMENT "Adding custom section"
)

add_custom_target()

# Цели, не создающие файлы
add_custom_target(doc
    COMMAND doxygen ${CMAKE_SOURCE_DIR}/Doxyfile
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Generating documentation"
)

# Зависимости
add_custom_target(package ALL
    DEPENDS myapp mylib doc
    COMMAND cpack -G ZIP
)

# Псевдоцели (как make clean)
add_custom_target(clean-all
    COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}
    COMMENT "Removing build directory"
)

Импорт таргетов

# Импорт библиотеки с настройкой
add_library(External::Lib STATIC IMPORTED)

# Разные пути для разных конфигураций
set_target_properties(External::Lib PROPERTIES
    IMPORTED_LOCATION_DEBUG "/path/to/lib_debug.a"
    IMPORTED_LOCATION_RELEASE "/path/to/lib_release.a"
    INTERFACE_INCLUDE_DIRECTORIES "/path/to/include"
    INTERFACE_COMPILE_DEFINITIONS "USE_EXTERNAL_LIB"
)

# Использование как обычного таргета
target_link_libraries(myapp PRIVATE External::Lib)

Современные практики (Modern CMake)

# 1. Используйте target_*, а не глобальные команды
# ПЛОХО:
include_directories(include)
add_executable(app main.cpp)
target_link_libraries(app mylib)

# ХОРОШО:
add_executable(app main.cpp)
target_include_directories(app PRIVATE include)
target_link_libraries(app PRIVATE mylib)

# 2. Используйте генераторные выражения для кроссплатформенности
target_compile_options(app PRIVATE
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

# 3. Экспортируйте зависимости правильно
add_library(mylib lib.cpp)
target_include_directories(mylib
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
    PRIVATE
        src
)

# 4. Используйте псевдонимы для таргетов
add_library(mylib SHARED lib.cpp)
add_library(MyProject::mylib ALIAS mylib)

# 5. Создайте конфигурационный файл
configure_file(config.h.in config.h @ONLY)
target_include_directories(app PRIVATE ${CMAKE_BINARY_DIR})

Команды для установки и упаковки

install()

# Установка таргетов
install(TARGETS myapp mylib
    EXPORT MyProjectTargets
    RUNTIME DESTINATION bin          # Исполняемые файлы
    LIBRARY DESTINATION lib          # Библиотеки (.so, .dylib)
    ARCHIVE DESTINATION lib          # Статические библиотеки (.a, .lib)
    INCLUDES DESTINATION include     # Заголовочные файлы
)

# Установка файлов
install(FILES
    README.md
    LICENSE
    DESTINATION .
)

install(DIRECTORY include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
)

# Установка экспорта
install(EXPORT MyProjectTargets
    FILE MyProjectConfig.cmake
    NAMESPACE MyProject::
    DESTINATION lib/cmake/MyProject