Введение
Основной цикл статей и цикл OpenCV (статья 561) уже использовали CMake без систематического разбора — настало время вернуться к самим основам и разобрать CMake как самостоятельную, мощную систему сборки. В этой статье разберём философию CMake — он не компилирует код сам, а генерирует файлы для другой, низкоуровневой системы сборки, и минимальную структуру CMakeLists.txt.
Концепция
CMake — это «мета-система сборки» (build system generator), а не сама система сборки — она читает декларативное описание проекта в файлах CMakeLists.txt и генерирует файлы для конкретной, целевой системы сборки (Makefile для Unix, файлы проекта Visual Studio, Ninja-файлы), которая уже непосредственно вызывает компилятор. Эта архитектура даёт переносимость — один и тот же CMakeLists.txt способен сгенерировать корректные файлы сборки для совершенно разных платформ и инструментов без изменения исходного описания проекта. Процесс сборки через CMake состоит из двух явных шагов — конфигурации (генерация файлов сборки в отдельной, обычно отдельной от исходников директории) и собственно сборки (вызов сгенерированной системы сборки), и современная, рекомендуемая практика использует «out-of-source» сборку — генерируемые файлы помещаются в отдельную директорию, не смешиваясь с исходным кодом проекта.
Пример кода: Обзор CMake
# CMakeLists.txt — минимальный, но полный пример
cmake_minimum_required(VERSION 3.16) # минимальная требуемая версия самого CMake
project(HelloWorld LANGUAGES CXX) # имя проекта и используемые языки
set(CMAKE_CXX_STANDARD 20) # используемый стандарт C++ (статья 551 цикла синтаксиса C/C++)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # ОБЯЗАТЕЛЬНОЕ требование — не "понижать" стандарт при недоступности
add_executable(HelloWorld main.cpp) # создание цели — исполняемого файла из указанных исходников
# Конфигурация и сборка — два РАЗДЕЛЬНЫХ шага, out-of-source сборка
mkdir build
cd build
cmake .. # конфигурация — генерация файлов сборки в текущей директории build/
cmake --build . # сборка — универсальная команда, вызывающая нужный инструмент под капотом
# Явный выбор конкретного генератора — для другой целевой системы сборки
cmake -G "Ninja" .. # генерация файлов для Ninja вместо системного генератора по умолчанию
cmake -G "Visual Studio 17 2022" .. # генерация полноценного решения Visual Studio
Пояснения к коду
cmake_minimum_required(VERSION 3.16) — обязательная первая строка любого CMakeLists.txt, явно фиксирующая минимальную версию самого CMake, необходимую для корректной интерпретации остальных команд файла, что особенно важно, поскольку синтаксис и поведение отдельных команд CMake эволюционировали между версиями (аналогично эволюции стандартов C++, статья 551 цикла синтаксиса). add_executable(HelloWorld main.cpp) показывает создание цели (target, статья 574) — центральную концепцию современного CMake, представляющую конкретный артефакт сборки (в данном случае — исполняемый файл) со своим собственным набором свойств и зависимостей. Разделение на cmake .. (конфигурация) и cmake --build . показывает универсальный, не зависящий от конкретного генератора способ выполнения сборки — команда --build сама определяет, какой именно низкоуровневый инструмент (make, ninja, msbuild) реально нужно вызвать для данной, уже сконфигурированной директории сборки, что освобождает разработчика от необходимости помнить точный синтаксис вызова каждого конкретного, низкоуровневого инструмента сборки отдельно.
Подводные камни
- Смешивание исходного кода с файлами, генерируемыми CMake («in-source» сборка, без отдельной директории
build/) — это исторически было распространённой практикой, но современный CMake настойчиво рекомендует избегать этого, поскольку генерируемые файлы (промежуточные объектные файлы, кеш CMake, статья 577) загромождают репозиторий с исходным кодом, легко могут быть случайно закоммичены в систему контроля версий, и существенно усложняют полную, «чистую» пересборку проекта (которая для out-of-source сборки сводится к простому удалению директорииbuild/). - Игнорирование явного указания
CMAKE_CXX_STANDARD_REQUIRED ON— без этой настройки, если запрошенный стандарт C++ недоступен для используемого компилятора, CMake может молчаливо «откатиться» к более старому, доступному стандарту, что может привести к неожиданным ошибкам компиляции кода, написанного с использованием возможностей более нового стандарта (статьи 541-550 цикла синтаксиса C/C++), без явного, понятного сообщения о реальной причине проблемы. - Зависимость от конкретного генератора по умолчанию, отличающегося между разными платформами (Makefile на Linux/macOS, Visual Studio на Windows), без явного учёта этой разницы при написании документации или скриптов автоматизации для проекта — для проектов, предполагающих сборку на разных платформах разными разработчиками, явное указание желаемого, согласованного для всей команды генератора (часто через Ninja, доступный кроссплатформенно) может дать более предсказуемое, согласованное поведение независимо от платформы конкретного разработчика.
- Незнание разницы между версией CMake, требуемой для конфигурации файла (
cmake_minimum_required), и версией, реально установленной в системе или CI-окружении (статья 446 DevOps-цикла) — попытка собрать проект с явно указанной минимальной версией CMake, превышающей реально доступную в окружении сборки, приведёт к понятной ошибке прямо на этапе конфигурации, и для CI-пайплайнов важно явно убедиться, что используемый образ/окружение имеет версию CMake, удовлетворяющую требованиям всех собираемых в этом окружении проектов.