Как устанавливать флаги компилятора в современном CMake?
Флаги задаются через target_compile_options(), target_compile_definitions() и target_compile_features() с областями PRIVATE/PUBLIC/INTERFACE. Глобальный CMAKE_CXX_FLAGS устарел и нарушает инкапсуляцию.
Установка флагов компилятора в современном CMake
В современном CMake (3.x) флаги компилятора передаются через свойства целей (target properties), а не через глобальные переменные вроде CMAKE_CXX_FLAGS. Ключевые команды:
target_compile_options()— добавляет опции компиляции к конкретной цели.target_compile_definitions()— добавляет макросы препроцессора.target_compile_features()— декларирует требования к стандарту языка.
Каждая из этих команд принимает область видимости: PRIVATE, PUBLIC или INTERFACE.
- PRIVATE — флаг применяется только к данной цели.
- PUBLIC — применяется к цели и ко всем, кто на неё ссылается через
target_link_libraries(). - INTERFACE — применяется только к потребителям цели, но не к ней самой (типично для header-only библиотек).
cmake_minimum_required(VERSION 3.20)
project(MyApp CXX)
add_executable(myapp main.cpp)
# Флаги только для этой цели
target_compile_options(myapp PRIVATE
-Wall
-Wextra
-Wpedantic
$<$<CONFIG:Release>:-O3>
$<$<CONFIG:Debug>:-g -fsanitize=address>
)
# Макрос, доступный и цели, и её потребителям
target_compile_definitions(myapp PUBLIC
APP_VERSION="1.0"
)
# Требование к стандарту C++20
target_compile_features(myapp PRIVATE cxx_std_20)
set_target_properties(myapp PROPERTIES CXX_EXTENSIONS OFF)
Генераторные выражения для конфиг-зависимых флагов
CMake поддерживает генераторные выражения (generator expressions) вида $<condition:value>, которые вычисляются на этапе генерации билд-системы. Это позволяет задавать разные флаги для Debug/Release без дублирования кода:
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<CONFIG:Release>>:-march=native>
)
Почему не стоит использовать CMAKE_CXX_FLAGS
Глобальная переменная CMAKE_CXX_FLAGS применяется ко всем целям в проекте, включая сторонние библиотеки, подключённые через add_subdirectory() или FetchContent. Это нарушает инкапсуляцию и может сломать чужой код. Кроме того, CMAKE_CXX_FLAGS не учитывает область видимости и не поддерживает генераторные выражения так же удобно.
Установка стандарта через set_target_properties
set_target_properties(myapp PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
Флаг CXX_STANDARD_REQUIRED YES вызовет ошибку конфигурации, если компилятор не поддерживает указанный стандарт. CXX_EXTENSIONS NO отключает GNU-расширения (-std=gnu++20 → -std=c++20).
Подводные камни
- Использование
CMAKE_CXX_FLAGS«загрязняет» все цели проекта, включая сторонние зависимости. - Флаги, специфичные для компилятора (например,
-march=nativeдля GCC), сломают сборку на MSVC — всегда оборачивайте их в$<CXX_COMPILER_ID:...>. - Дублирование
-WallчерезCMAKE_CXX_FLAGSиtarget_compile_options()одновременно приводит к двойной передаче флага. - Область видимости
PUBLICу жёстких флагов (например,-Werror) навязывает их потребителям библиотеки — они могут не компилироваться. target_compile_features(cxx_std_20)и явноеset_target_properties(CXX_STANDARD 20)могут конфликтовать: используйте одно из двух.- Генераторные выражения не работают внутри
if()— только в командах, принимающих их явно. - Флаг санитайзера (
-fsanitize=address) нужно добавить и вtarget_link_options(), иначе линкер не подключит runtime библиотеку ASan. - На Windows пробелы в путях к компилятору ломают конкатенацию строк в
CMAKE_CXX_FLAGS— ещё одна причина использовать target-команды.
Common mistakes
- Объяснять флаги компилятора в modern CMake только по синтаксису, без жизненного цикла и стоимости.
- Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
- Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
- Трактовать CMake как shell-скрипт вместо описания графа таргетов.
What the interviewer is testing
- Кандидат формулирует точную модель для флаги компилятора в modern CMake, а не только определение.
- Пример компилируем, безопасен по lifetime и соответствует версии технологии.
- Названы trade-off, ограничения и диагностируемые симптомы ошибки.
- Разделяет configure/generate/build и использует target-based подход.