CMakeMiddleTechnical
В чём разница между set() с CACHE и без?
set(VAR val) создаёт обычную переменную, видимую только в текущей области и не сохраняемую. set(VAR val CACHE STRING "desc") сохраняет значение в CMakeCache.txt, доступна через -DVAR=val и глобально видна, но не перезаписывает уже кешированное значение без FORCE.
set() с CACHE и без: два типа переменных CMake
CMake разделяет переменные на два уровня: обычные (normal variables) и кешированные (cache variables). Они хранятся в разных местах, имеют разный приоритет и разное время жизни.
Обычная переменная (без CACHE)
set(MY_VAR "hello")
- Хранится в памяти во время выполнения CMakeLists.txt.
- Видна только в текущей области видимости и вложенных (в функциях, add_subdirectory с оговорками).
- Не сохраняется в
CMakeCache.txt. - Не доступна пользователю через
-Dфлаг командной строки. - Пересоздаётся при каждом запуске
cmake.
Кешированная переменная (с CACHE)
set(MY_VAR "hello" CACHE STRING "Description of MY_VAR")
- Сохраняется в
CMakeCache.txtв build-директории. - Пользователь может переопределить через
cmake -DMY_VAR=worldили через cmake-gui. - После первой установки НЕ перезаписывается повторным
set(... CACHE ...)без флагаFORCE. - Глобально видна во всём проекте, включая все поддиректории.
# Типы кешированных переменных
set(MY_STRING "text" CACHE STRING "A string value")
set(MY_BOOL ON CACHE BOOL "A boolean flag")
set(MY_INT 42 CACHE STRING "An integer") # CMake хранит как строку
set(MY_PATH "/usr" CACHE PATH "A directory path")
set(MY_FILE "a.txt" CACHE FILEPATH "A file path")
Приоритет и перекрытие
Правило приоритета в CMake:
- Обычная переменная (если существует в текущей области) — наивысший приоритет.
- Переменная среды (через
$ENV{VAR}). - Кешированная переменная — низший приоритет.
set(MY_VAR "cache_value" CACHE STRING "desc")
# Пользователь передал -DMY_VAR=user_value, кеш содержит "user_value"
set(MY_VAR "normal_value") # Обычная переменная перекрывает кеш!
message(STATUS "MY_VAR = ${MY_VAR}") # Выведет: normal_value
# Это частая ловушка: разработчик думает, что задаёт default для кеша,
# но фактически блокирует возможность пользователя изменить значение
Флаг FORCE
# FORCE принудительно перезаписывает кешированное значение
# даже если пользователь уже задал его через -D
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
# Внимание: FORCE отбирает контроль у пользователя — используйте осторожно
Практический пример: опция сборки
cmake_minimum_required(VERSION 3.21)
project(MyApp)
# Правильно: кешированная переменная, пользователь может изменить
set(MYAPP_LOG_LEVEL "INFO" CACHE STRING "Log level (DEBUG/INFO/WARNING/ERROR)")
set_property(CACHE MYAPP_LOG_LEVEL PROPERTY STRINGS DEBUG INFO WARNING ERROR)
# Внутренняя переменная — не показывается в cmake-gui
set(MYAPP_INTERNAL_FLAG "computed" CACHE INTERNAL "")
# Обычная переменная для промежуточных вычислений
set(sources_list src/main.cpp src/utils.cpp)
add_executable(myapp ${sources_list})
target_compile_definitions(myapp PRIVATE
MYAPP_LOG_LEVEL=${MYAPP_LOG_LEVEL}
)
# Пользователь задаёт при configure
cmake -S . -B build -DMYAPP_LOG_LEVEL=DEBUG
# Просмотр всех кешированных переменных
cmake -S . -B build -LH
Подводные камни
- set() без CACHE перекрывает -D флаги пользователя: если в CMakeLists.txt есть
set(MY_VAR "default")после того, как пользователь передал-DMY_VAR=custom, обычная переменная выиграет в текущей области видимости. - Кеш не обновляется при изменении default в CMakeLists.txt: старое значение остаётся в CMakeCache.txt. Нужно либо удалить кеш, либо использовать FORCE.
- FORCE в CMakeLists.txt ломает повторную конфигурацию: каждый cmake-вызов перезаписывает значение, что мешает CI-скриптам, которые передают -D.
- Область видимости обычных переменных в функциях: функции в CMake имеют собственную область — изменения внутри функции не видны снаружи без PARENT_SCOPE.
- CACHE INTERNAL скрывает переменную от пользователя: переменные типа INTERNAL не отображаются в cmake-gui и ccmake, что полезно для внутреннего состояния, но скрывает их от отладки.
- Переменные среды не кешируются:
$ENV{CC}читается при каждом configure, но не сохраняется в CMakeCache.txt, в отличие от CMAKE_C_COMPILER.
Common mistakes
- Объяснять set CACHE и обычный set только по синтаксису, без жизненного цикла и стоимости.
- Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
- Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
- Трактовать CMake как shell-скрипт вместо описания графа таргетов.
What the interviewer is testing
- Кандидат формулирует точную модель для set CACHE и обычный set, а не только определение.
- Пример компилируем, безопасен по lifetime и соответствует версии технологии.
- Названы trade-off, ограничения и диагностируемые симптомы ошибки.
- Разделяет configure/generate/build и использует target-based подход.