Продвинутые возможности CMake
После освоения основ CMake продвинутые возможности помогут вам более гибко управлять конфигурацией сборки сложных проектов.
В этой статье рассматриваются шесть основных тем: пользовательские модули, многоконфигурационные сборки, расширенный поиск пакетов, пользовательские шаги сборки, кросс-платформенная кросс-компиляция и настройка свойств целей.
Общий обзор содержания:
| Тема | Ключевая возможность | Типичный сценарий применения |
|---|---|---|
| Пользовательские модули и скрипты | Инкапсуляция переиспользуемой логики CMake | Унифицированные параметры компилятора, пользовательская логика поиска, общие для команды правила сборки |
| Конфигурации и цели сборки | Управление несколькими конфигурациями и целями | Переключение Debug/Release, сборка нескольких выходных артефактов |
| Расширенный поиск и конфигурация | Гибкий поиск внешних зависимостей | Поиск конкретных компонентов, передача информации о сборке через файлы конфигурации |
| Пользовательские шаги сборки | Расширение процесса сборки | Генерация кода, обработка ресурсов, автоматизация после сборки |
| Кроссплатформенность и кросс-компиляция | Многоплатформенные сборки | Разработка для встраиваемых систем, компиляция для разных операционных систем |
| Свойства и конфигурация целей | Тонкий контроль над компиляцией и компоновкой | Установка уровней предупреждений и путей компоновки для конкретных целей |
Пользовательские модули и скрипты CMake
Когда нескольким файлам CMakeLists.txt нужно повторять одну и ту же логику, инкапсуляция этой логики в пользовательские модули может значительно сократить дублирование кода.
Суть пользовательских модулей и скриптов состоит в том, чтобы вынести часто используемые функции, макросы и конфигурации CMake в отдельные файлы для повторного использования в нескольких проектах.
Пользовательские модули CMake
Пользовательский модуль — это файл.cmakeс расширением .cmake, который определяет функции и макросы CMake, загружаемые через include().
Шаги создания:
| Шаг | Действие | Описание |
|---|---|---|
| 1 | Создайтеcmake/Каталог |
Создайте его в корневом каталоге проекта, чтобы централизованно хранить пользовательские модули. |
| 2 | Создайте файл модуля | Напримерcmake/MyModule.cmakeНапишите пользовательские функции |
| 3 | Расширьте путь поиска модулей | Зарегистрируйте этот каталог через CMAKE_MODULE_PATH в CMakeLists.txt |
| 4 | Загрузите модуль | Используйте include() для импорта модуля и вызова его функций |
Пример пользовательского модуля — MyModule.cmake:
Пример
# Пользовательский модуль: предоставляет вспомогательные функции, общие для проекта
# Функция: единообразно задаёт параметры предупреждений компилятора для всех целей
# Параметр ARG_TARGET: имя цели, для которой нужно задать предупреждения
function(example_set_warnings ARG_TARGET)
# Задать разные флаги предупреждений в зависимости от типа компилятора
if(MSVC)
# Компилятор MSVC: включить уровень предупреждений /W4
target_compile_options(${ARG_TARGET} PRIVATE /W4)
else()
# Компилятор GCC / Clang: включить общие предупреждения
target_compile_options(${ARG_TARGET} PRIVATE -Wall -Wextra -Wpedantic)
endif()
endfunction()
# Функция: вывод базовой информации о сборке для цели
function(example_print_target_info ARG_TARGET)
message(STATUS "Имя цели: ${ARG_TARGET}")
message(STATUS "Список исходных файлов: $<TARGET_PROPERTY:${ARG_TARGET},SOURCES>")
endfunction()
Загрузите и используйте модуль в CMakeLists.txt:
Пример
cmake_minimum_required(VERSION 3.10)
project(MyProject CXX)
# Добавить каталог cmake/ в путь поиска модулей
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Загрузить пользовательский модуль
include(MyModule)
# Добавить исполняемую цель
add_executable(MyApp main.cpp)
# Вызвать пользовательскую функцию в модуле, чтобы задать параметры предупреждений для MyApp
example_set_warnings(MyApp)
# Вызвать пользовательскую функцию в модуле, чтобы вывести информацию о цели
example_print_target_info(MyApp)
CMAKE_MODULE_PATH— это список путей поиска, используемый CMake при поиске файлов модулей. CMake сначала выполняет поиск по встроенному пути модулей, а если не находит, ищет по путям, указанным в CMAKE_MODULE_PATH.
Использование пользовательских скриптов CMake
Пользовательские скрипты похожи на модули, но обычно используются для выполнения операций конфигурации, а не для определения повторно используемых функций.
Скрипты можно загружать напрямую с помощью include(), и CMake выполняет каждую команду в них по порядку.
Создайте файл скрипта config.cmake:
Пример
# Пользовательский скрипт: централизованное управление параметрами конфигурации проекта
# Установить тип сборки по умолчанию в Release
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Тип сборки" FORCE)
message(STATUS "Тип сборки не указан, по умолчанию используется Release")
endif()
# Установить разные параметры компиляции в зависимости от типа сборки
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Включить режим Debug: отключить оптимизации, включить отладочные символы")
else()
message(STATUS "Включить режим Release: включить оптимизацию O2")
endif()
Вызовите скрипт в CMakeLists.txt:
# 使用绝对路径加载脚本
include(${CMAKE_SOURCE_DIR}/config.cmake)
Конфигурации и цели сборки
CMake поддерживает управление несколькими конфигурациями сборки (такими как Debug и Release) в одном проекте и определение нескольких целей сборки.
Многоконфигурационные генераторы
Разные генераторы CMake обрабатывают конфигурации по-разному.
Например, Visual Studio и Xcodeмногоконфигурационные генераторыпозволяют переключать Debug/Release в одном каталоге сборки, тогда как Unix Makefiles и т. п.одноконфигурационные генераторыдолжны определять тип сборки на этапе конфигурации.
| Тип генератора | Типичные генераторы | Способ указания конфигурации |
|---|---|---|
| Одноконфигурационные | Unix Makefiles、Ninja | На этапе конфигурации с помощью-DCMAKE_BUILD_TYPE=Releaseуказать |
| Мультиконфигурация | Visual Studio, Xcode | Во время сборки через--config Releaseswitch |
Задайте конфигурацию по умолчанию в CMakeLists.txt:
# 仅对单配置生成器生效,多配置生成器会忽略此变量 set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type")
CMAKE_BUILD_TYPE действует только для одноконфигурационных генераторов. Если вы используете Visual Studio, переключайте конфигурации с помощью cmake --build . --config Release или в IDE.
Цели сборки
В проекте CMake можно определить несколько целей сборки, и каждая цель независимо настраивает параметры компиляции и зависимости компоновки.
Это позволяет создавать несколько исполняемых файлов или несколько библиотек за одну сборку.
Пример
add_executable(MyApp src/main.cpp)
add_executable(MyTool src/tool.cpp)
# Задание разных макросов компиляции для разных целей для управления условной компиляцией
set_target_properties(MyApp PROPERTIES COMPILE_DEFINITIONS "APP_MODE")
set_target_properties(MyTool PROPERTIES COMPILE_DEFINITIONS "TOOL_MODE")
# Подключение разных библиотек для разных целей
target_link_libraries(MyApp PRIVATE MyLib)
target_link_libraries(MyTool PRIVATE MyLib CLI11::CLI11)
Расширенный поиск и конфигурация
Помимо базового использования find_package, CMake также поддерживает указание компонентов, точный контроль версий и передачу информации о сборке через шаблоны файлов конфигурации.
Расширенное использование find_package
COMPONENTSЭто ключевое слово позволяет находить только определённые подмодули библиотеки, а не весь пакет.
Это очень важно для больших библиотек, таких как Boost — импорт по требованию позволяет избежать компоновки ненужных компонентов.
Пример
# filesystem внутренне зависит от system, поэтому оба должны быть объявлены
find_package(Boost REQUIRED COMPONENTS filesystem system)
# Проверить, найдены ли требуемые компоненты
if(Boost_FOUND)
message(STATUS "Версия Boost: ${Boost_VERSION}")
message(STATUS "Каталоги включаемых файлов Boost: ${Boost_INCLUDE_DIRS}")
# Слинковать каждый требуемый компонент отдельно
target_link_libraries(MyApp PRIVATE
Boost::filesystem
Boost::system
)
endif()
Укажите пути поиска (для нестандартных мест установки):
# 方式一:通过变量指定根目录 cmake .. -DBOOST_ROOT=/path/to/custom/boost # 方式二:在 CMakeLists.txt 中预先设置 set(BOOST_ROOT "/path/to/custom/boost") find_package(Boost REQUIRED COMPONENTS filesystem)
Файлы конфигурации и параметры сборки
configure_file()Директива позволяет внедрять значения переменных CMake в файлы кода, благодаря чему информацию о конфигурации можно передавать на этапе компиляции.
Это очень полезно, когда нужно включить в программу такую информацию, как номера версий, время сборки и т. п.
Создайте файл шаблона конфигурации config.h.in:
Пример
// Шаблонный файл конфигурации CMake, генерирует config.h с помощью configure_file()
// Номер версии проекта (заменяется переменной CMake @PROJECT_VERSION@)
#define EXAMPLE_VERSION "@PROJECT_VERSION@"
// Тип сборки (Debug или Release)
#define EXAMPLE_BUILD_TYPE "@CMAKE_BUILD_TYPE@"
// Временная метка сборки
#define EXAMPLE_BUILD_TIMESTAMP "@BUILD_TIMESTAMP@"
Настройте и сгенерируйте заголовочный файл в CMakeLists.txt:
Пример
string(TIMESTAMP BUILD_TIMESTAMP "%Y-%m-%d %H:%M:%S")
# Заменить @variables@ в config.h.in на значения переменных CMake
# Вывод в config.h в каталоге сборки
configure_file(config.h.in config.h)
# Добавить каталог сборки в путь поиска заголовочных файлов, чтобы исходные файлы могли найти config.h
include_directories(${CMAKE_BINARY_DIR})
Использовать сгенерированную конфигурацию в исходных файлах:
// 在 main.cpp 中包含生成的配置文件 #include "config.h" std::cout << "EXAMPLE Version: " << EXAMPLE_VERSION << std::endl; std::cout << "Build Type: " << EXAMPLE_BUILD_TYPE << std::endl;
Генерация пользовательских шагов сборки
Помимо стандартной компиляции и компоновки, CMake позволяет вставлять пользовательские операции в процесс сборки, такие как генерация кода, копирование ресурсов и уведомления после сборки.
Пользовательские команды
add_custom_command()Используется для определения пользовательской команды, которая выполняется, когда файл нужно обновить.
Основная концепция этой команды —управление зависимостями— команда выполняется только когда файл OUTPUT не существует или файл DEPENDS был обновлён.
Пример
# Выполнять только если generated_file.cpp не существует или input_template.txt был обновлён
add_custom_command(
# Выходной файл (CMake проверяет этот файл, чтобы решить, перезапускать ли команду)
OUTPUT ${CMAKE_BINARY_DIR}/generated_file.cpp
# Команда для выполнения (здесь cmake -E используется для выполнения встроенной операции генерации файла)
COMMAND ${CMAKE_COMMAND}
-E echo "// Автоматически сгенерированный исходный файл" > ${CMAKE_BINARY_DIR}/generated_file.cpp
# Файл зависимости (команда перезапускается при изменении этого файла)
DEPENDS ${CMAKE_SOURCE_DIR}/input_template.txt
# Сообщение, отображаемое при выполнении
COMMENT "Создание исходного файла из шаблона..."
)
Команда-триггер этапа сборки — выполняется после завершения компоновки:
Пример
add_custom_command(
TARGET MyApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:MyApp>
${CMAKE_SOURCE_DIR}/deploy/
COMMENT "Скопировать MyApp в каталог развёртывания"
)
Пользовательские цели
add_custom_target()Определяет цель сборки, которая не создаёт выходной файл; обычно используется для объединения нескольких пользовательских команд.
Если не используется ключевое слово ALL, пользовательские цели не будут выполняться при сборке по умолчанию; необходимо явно указать имя цели.
Пример
# ALL означает, что она будет выполняться автоматически при сборке по умолчанию (make)
add_custom_target(generate_code ALL
# Зависит от файла OUTPUT, определённого ранее
DEPENDS ${CMAKE_BINARY_DIR}/generated_file.cpp
)
# Пользовательская цель: выполнять вспомогательные задачи (не будет выполняться при сборке по умолчанию)
add_custom_target(deploy
COMMAND ${CMAKE_COMMAND} -E echo "Развертывание на сервер..."
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_BINARY_DIR}/bin
/opt/example/deploy/
COMMENT "Выполнить операцию развертывания"
)
# Запускать эту цель отдельно во время сборки
# $ cmake --build . --target deploy
add_custom_command и add_custom_target различаются: первая задаёт правило для «как генерировать файлы» и запускается по зависимостям; вторая задаёт цель-действие для «что делать» и должна быть явно вызвана либо запускается автоматически, когда используется ALL.
Кроссплатформенность и кросс-компиляция
Одно из ключевых преимуществ CMake — кроссплатформенная поддержка: один и тот же CMakeLists.txt может генерировать соответствующие файлы сборки в разных операционных системах и архитектурах.
Кроссплатформенные сборки
CMake автоматически определяет компилятор и системное окружение текущей платформы и генерирует соответствующие файлы сборки.
Вы также можете явно указать информацию о целевой платформе через переменные.
| Переменная | Описание | Пример значения |
|---|---|---|
CMAKE_SYSTEM_NAME |
Целевая операционная система | Linux、Windows、Darwin、Android |
CMAKE_SYSTEM_PROCESSOR |
Целевая архитектура процессора | x86_64、arm、aarch64 |
CMAKE_C_COMPILER |
Путь к компилятору C | /usr/bin/arm-linux-gnueabihf-gcc |
CMAKE_CXX_COMPILER |
Путь к компилятору C++ | /usr/bin/arm-linux-gnueabihf-g++ |
Кросс-компиляция
Кросс-компиляция — это компиляция программ, которые выполняются на другой архитектуре (например, ARM), на текущей платформе (например, x86_64 Linux).
CMake используетфайл цепочки инструментов (Toolchain File)для указания компилятора, информации о системе и конфигурации компоновщика, необходимых для кросс-компиляции.
Создайте файл цепочки инструментов toolchain.cmake:
Пример
# Файл конфигурации цепочки инструментов для кросс-компиляции ARM Linux
# Целевая система — Linux
set(CMAKE_SYSTEM_NAME Linux)
# Целевая архитектура процессора — ARM
set(CMAKE_SYSTEM_PROCESSOR arm)
# Укажите кросс-компилятор (обязательно: скорректируйте путь в соответствии с фактическим расположением инструментальной цепочки)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)
# Укажите путь к корневой файловой системе целевой системы (необязательно, используется для поиска библиотек и заголовочных файлов)
set(CMAKE_FIND_ROOT_PATH /path/to/arm-sysroot)
# При поиске программ выполняйте поиск только в путях целевой системы (не в путях хоста)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# При поиске библиотек выполняйте поиск только в путях целевой системы
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# При поиске заголовочных файлов искать только в путях целевой системы
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Кросс-компиляция с использованием файла цепочки инструментов:
# 配置时通过 -DCMAKE_TOOLCHAIN_FILE 指定工具链文件 mkdir build_arm && cd build_arm cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. # 使用生成的 Makefile 编译(将调用 ARM 交叉编译器) make
Файл цепочки инструментов должен быть указан при первом запуске cmake. Если CMakeCache.txt уже существует, необходимо удалить каталог сборки и выполнить повторную настройку, чтобы переключить цепочки инструментов.
Свойства цели и конфигурация
CMake позволяет тонко управлять опциями компиляции, опциями компоновки и различными свойствами для каждой цели, что является рекомендуемой практикой в современном CMake.
Свойства цели
set_target_properties()Используется для установки свойств уровня цели, которые влияют на поведение компилятора и компоновщика.
| Свойство | Описание | Пример |
|---|---|---|
COMPILE_OPTIONS |
Установка опций компиляции | -Wall、-O2 |
COMPILE_DEFINITIONS |
Установка макросов препроцессора | DEBUG、VERSION=2 |
LINK_FLAGS |
Задать флаги компоновщика | -L/path/to/lib |
OUTPUT_NAME |
Изменить имя выходного файла | Имя цели — MyApp, а имя выходного файла — my_app |
Пример
add_executable(MyApp main.cpp)
# Задать параметры компиляции и флаги компоновщика для цели
set_target_properties(MyApp PROPERTIES
COMPILE_OPTIONS "-Wall;-Wextra"
COMPILE_DEFINITIONS "EXAMPLE_DEBUG"
LINK_FLAGS "-L/usr/local/lib"
OUTPUT_NAME "my_app"
)
# Приведённый выше set_target_properties эквивалентен следующим трём командам:
# target_compile_options(MyApp PRIVATE -Wall -Wextra)
# target_compile_definitions(MyApp PRIVATE EXAMPLE_DEBUG)
# target_link_options(MyApp PRIVATE -L/usr/local/lib)
В современном CMake рекомендуется использовать target_compile_options(), target_compile_definitions() и target_link_options() вместо set_target_properties(), поскольку они поддерживают управление видимостью PUBLIC/PRIVATE/INTERFACE и делают зависимости более понятными.
Пользовательские параметры компиляции и компоновки
Использование команд семейства target позволяет более точно контролировать область распространения параметров.
Пример
# PRIVATE: только сам MyApp использует эти параметры при компиляции
target_compile_options(MyApp PRIVATE -Wall -Wextra -Wpedantic)
# Задать определения препроцессора для MyApp
# Эквивалентно записи #define EXAMPLE_VERSION "1.0" в исходном файле
target_compile_definitions(MyApp PRIVATE EXAMPLE_VERSION="1.0")
# Установить параметры компоновки для MyApp
target_link_options(MyApp PRIVATE -L/usr/local/lib)
# Если это библиотека, использование PUBLIC позволяет целям, которые компонуются с этой библиотекой, также наследовать эти параметры
# Например: любая цель, использующая MyLib, автоматически включит C++17
target_compile_features(MyLib PUBLIC cxx_std_17)
Примечания
CMAKE_BUILD_TYPE допустим только для одноконфигурационных генераторов.Если проект должен поддерживать и Makefile, и Visual Studio, не полагайтесь на значение CMAKE_BUILD_TYPE. Используйте генераторные выражения
$<$<CONFIG:Debug>:DEBUG_VALUE>чтобы динамически переключаться в зависимости от фактической конфигурации.
Предпочитайте команды серии target_xxx.Хотя set_target_properties и мощна, в ней отсутствует управление видимостью. В сценариях, требующих семантики PUBLIC/PRIVATE/INTERFACE, лучше использовать специализированные команды, такие как target_compile_options.
Файл инструментальной цепочки должен быть указан при первом вызове cmake.При кросс-компиляции повторное указание инструментальной цепочки в каталоге с существующим CMakeCache.txt может привести к неполной конфигурации. Всегда настраивайте кросс-компиляцию в чистом каталоге сборки.
Другие расширенияВ пользовательских модулях рекомендуется добавлять префикс к именам функций.Избегайте конфликтов имён со встроенными командами CMake или другими модулями. Например, используйте имя проекта как префикс: myproject_set_warnings().