Cmake

Переменные и кэш CMake: разница, scope, CACHE-переменные

Введение

Статья 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, важно для предсказуемого поведения системы сборки.