Введение
Статья 576 уже использовала переменные (CMAKE_CURRENT_SOURCE_DIR) без подробного разбора — в этой статье разберём систему переменных CMake систематически: обычные переменные с их областью видимости, и кэш-переменные, сохраняющиеся между отдельными запусками конфигурации и доступные для настройки пользователем сборки.
Концепция
Обычная переменная CMake (set(MY_VAR "значение")) действует в пределах текущей директории CMakeLists.txt и автоматически распространяется на вложенные директории, подключаемые через add_subdirectory, но НЕ распространяется обратно, «вверх» к родительской директории — это похоже на область видимости блоков в C/C++ (статья 513 цикла синтаксиса), хотя с важными отличиями в деталях наследования между уровнями вложенности. CACHE-переменная (set(MY_VAR "значение" CACHE STRING "Описание")) сохраняется в специальном файле CMakeCache.txt директории сборки между отдельными запусками cmake, и, что особенно важно, доступна для изменения пользователем напрямую (через cmake -DMY_VAR=новое_значение, или через графические инструменты конфигурации, как cmake-gui/ccmake) без необходимости редактировать сам CMakeLists.txt — это типичный механизм для предоставления пользователю проекта настраиваемых опций сборки (как уже использованный в статье 574 option(BUILD_SHARED_LIBS ...), который технически реализован именно как булева CACHE-переменная).
Пример кода: Переменные и кэш CMake
# Обычная переменная — область видимости текущей директории и вложенных поддиректорий
set(PROJECT_VERSION_STRING "1.2.3")
add_subdirectory(modules) # PROJECT_VERSION_STRING ВИДНА внутри modules/CMakeLists.txt
# Изменение переменной ВНУТРИ modules/CMakeLists.txt НЕ повлияет на значение здесь, в родительской директории —
# распространение видимости работает только "ВНИЗ", не "ВВЕРХ"
# CACHE-переменная — настраиваемая опция сборки, видимая пользователю
set(ENABLE_TESTING_SUPPORT ON CACHE BOOL "Собирать модульные тесты проекта")
if(ENABLE_TESTING_SUPPORT)
add_subdirectory(tests)
endif()
# option() — упрощённый, идиоматичный синтаксис специально для булевых CACHE-переменных
option(ENABLE_LOGGING "Включить подробное логирование" OFF)
# Изменение CACHE-переменной пользователем БЕЗ редактирования CMakeLists.txt
cmake -DENABLE_TESTING_SUPPORT=OFF -DENABLE_LOGGING=ON ..
# Просмотр и интерактивное изменение всех текущих CACHE-переменных
ccmake .. # текстовый интерфейс
cmake-gui .. # графический интерфейс
Пояснения к коду
Пример с PROJECT_VERSION_STRING, видимым внутри modules/CMakeLists.txt после add_subdirectory, но не позволяющим обратное распространение изменений, показывает специфичную для CMake модель областей видимости — это отличается от обычных переменных C++ (статья 513 цикла синтаксиса), и понимание именно этой однонаправленности важно для проектов со сложной, многоуровневой структурой поддиректорий. set(ENABLE_TESTING_SUPPORT ON CACHE BOOL "...") против эквивалентного, более компактного option(ENABLE_LOGGING "..." OFF) показывает два равноценных синтаксиса для того же концептуального механизма — option() — это просто более короткая, идиоматичная обёртка специально для булевых CACHE-переменных, не предоставляющая дополнительной функциональности по сравнению с полным синтаксисом set(... CACHE BOOL ...). Команда cmake -DENABLE_TESTING_SUPPORT=OFF .. показывает практическую ценность CACHE-переменных — конечный пользователь, собирающий проект, может настроить эти параметры непосредственно через командную строку при первой конфигурации, без необходимости вообще редактировать исходный CMakeLists.txt проекта.
Подводные камни
- Попытка изменить значение уже установленной CACHE-переменной через обычный
set()без явногоFORCE, ожидая, что это изменение реально применится — после первой конфигурации значение CACHE-переменной сохраняется вCMakeCache.txt, и последующий обычныйset(MY_VAR "новое_значение")(безCACHEиFORCE) создаёт лишь обычную, не кэшированную переменную с тем же именем, видимую только в текущей области CMakeLists.txt-файлов, тогда как реальная CACHE-переменная с этим именем остаётся неизменной — для реального изменения уже существующей CACHE-переменной программно нужен явныйset(MY_VAR "значение" CACHE TYPE "Описание" FORCE). - Забытое удаление директории сборки (или конкретно файла
CMakeCache.txt) при существенном изменении структуры проекта, особенно при смене используемого компилятора или генератора — устаревшие, «застрявшие» значения CACHE-переменных от предыдущей конфигурации (особенно пути к компилятору или другим инструментам, обнаруженным CMake автоматически при первой конфигурации) могут привести к неожиданным, трудно диагностируемым проблемам при попытке изменить фундаментальные аспекты конфигурации сборки без полной, «чистой» повторной конфигурации с самого начала. - Чрезмерное использование обычных, не кэшированных глобальных переменных для передачи данных между удалёнными друг от друга частями сложной, многомодульной структуры
CMakeLists.txt, вместо более явных, контролируемых механизмов (передача через параметры функций/макросов CMake, или явные свойства целей, статья 576) — это аналогично общему антипаттерну избыточного использования глобальных переменных в обычном коде C++ (статья 513 цикла синтаксиса), создавая неявные, трудно прослеживаемые зависимости между логически удалёнными частями описания сборки проекта. - Игнорирование того, что значения, переданные через
cmake -D...в командной строке, автоматически становятся CACHE-переменными, даже если соответствующая переменная не была явно объявлена как CACHE в самомCMakeLists.txt— это может привести к не вполне ожидаемому поведению, если в кодеCMakeLists.txtиспользуется обычная, не кэшированная переменная с тем же именем, которую разработчик не ожидал, что пользователь сборки сможет переопределить через командную строку, и явное, осознанное проектирование того, какие именно переменные предназначены для пользовательской настройки (и должны быть оформлены черезoption()/CACHE), а какие — исключительно для внутренней логики самогоCMakeLists.txt, важно для предсказуемого поведения системы сборки.