CMake і vcpkg

Конфігурації збірки: Debug, Release і вирази-генератори

Типи збірки Debug, Release, RelWithDebInfo та MinSizeRel, реальні прапорці компілятора для кожного, налаштування через CMAKE_BUILD_TYPE, вирази-генератори $<...>, відмінність single-config та multi-config генераторів.

Конфігурації збірки: Debug, Release і вирази-генератори

🎯 Мета лекції

  • Розрізняти Debug, Release, RelWithDebInfo та MinSizeRel і розуміти, які прапорці вони додають.
  • Встановлювати тип збірки з командного рядка та задавати значення за замовчуванням.
  • Налаштовувати прапорці й макроси окремо для кожної конфігурації.
  • Знати, чому на Visual Studio і на Makefiles конфігурація задається по-різному.

🔑 Ключові терміни

  • Конфігурація (configuration, тип збірки): набір прапорців компілятора для певної мети.
  • Оптимізація (optimization): перетворення коду компілятором для швидкості або розміру.
  • Debug-інформація: таблиці відповідності машинного коду й рядків вихідного файлу.
  • Вираз-генератор (generator expression): запис $<...>, який обчислюється при генерації файлів збірки.

Одну й ту саму програму можна скомпілювати двічі: для розробника і для користувача. У першому випадку потрібен відладчик і зрозумілі повідомлення про помилки, у другому швидкість. Це різні конфігурації, і CMake вміє перемикатися між ними без змін коду.

Що таке конфігурації збірки

CMake має чотири стандартні конфігурації:

КонфігураціяПризначенняОптимізаціяВідладочна інформація
DebugРозробка й налагодженняНемаєЄ
ReleaseВипуск для користувачівМаксимальнаНемає
RelWithDebInfoВипуск, який можна налагоджуватиПомірнаЄ
MinSizeRelМінімальний розмір файлуНа розмірНемає
  • Debug: програма містить інформацію для відладчика, тому можна ставити точки зупинки й бачити значення змінних. Оптимізація вимкнена, тому компілюється швидко, але працює повільніше. Файл більший.
  • Release: максимальна оптимізація. Швидка програма меншого розміру, але налагоджувати важко: компілятор міг змінити порядок і викинути змінні.
  • RelWithDebInfo: оптимізація плюс відладочна інформація. Зручно, коли помилка проявляється лише у Release-збірці, і потрібно отримати стек викликів.
  • MinSizeRel: оптимізація на розмір. Використовується для вбудованих систем і там, де обмежено пам'ять.

Які прапорці додає CMake насправді

Конфігурація — це лише назва, яка вмикає відповідну змінну CMAKE_CXX_FLAGS_<КОНФІГУРАЦІЯ>. Перевіримо, які прапорці виходять на практиці. Один і той самий проєкт зібрано чотири рази (компілятор AppleClang, прапорці з make VERBOSE=1):

прапорці компілятора за типами збірки
Debug -g
Release -O3 -DNDEBUG
RelWithDebInfo -O2 -g -DNDEBUG
MinSizeRel -Os -DNDEBUG

Звідси кілька висновків:

  • У Debug немає -O0: оптимізація вимкнена тим, що вона просто не додається (на GCC та Clang рівень за замовчуванням і є -O0).
  • У всіх конфігураціях, крім Debug, визначено макрос NDEBUG. Саме він вимикає assert() зі стандартної бібліотеки.
  • -O3, -O2, -Os задають рівні оптимізації: найшвидший, помірний та на розмір.

Для MSVC набори інші (/Od /Zi /RTC1 у Debug, /O2 /DNDEBUG у Release), але ідея та сама.

Через NDEBUG у Release-збірці всі виклики assert() зникають із програми. Тест, написаний на assert, у Release завжди «проходить», бо перевірок немає. Тому запускайте такі тести в Debug-збірці або користуйтеся тестовим фреймворком (Google Test, Catch2), у якому перевірки не залежать від NDEBUG.

Як вибрати конфігурацію

З командного рядка

Для генераторів з однією конфігурацією (Makefiles, Ninja) тип вибирається на етапі конфігурації:

# Debug
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug

# Release
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# Release з відладочною інформацією
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo

# Мінімальний розмір
cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel
Якщо CMAKE_BUILD_TYPE не вказано, змінна залишається порожньою. Тоді прапорці конфігурацій не додаються взагалі: збірка не оптимізована і не має відладочної інформації. Це четвертий, «безіменний» режим, який легко сплутати з Debug.

У CMakeLists.txt: значення за замовчуванням

cmake_minimum_required(VERSION 3.10)
project(MyApp)

# Якщо тип не вказано, використати Debug
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
    message(STATUS "Build type not specified, using Debug")
endif()

message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

add_executable(MyApp main.cpp)

У команді set(... CACHE STRING "..." FORCE) значення записується в кеш, тому його видно у cmake -L і GUI. Слово FORCE потрібне, щоб перезаписати порожнє значення, що вже є в кеші.

Такий блок коректний лише для генераторів з однією конфігурацією. У Visual Studio та Xcode CMAKE_BUILD_TYPE не використовується, а тип обирається при збірці. Для таких генераторів перевірте CMAKE_CONFIGURATION_TYPES або оберіть підхід із виразами-генераторами нижче.

Налаштування для кожної конфігурації

Вирази-генератори

Вираз-генератор записується як $<...> і обчислюється при генерації файлів збірки, коли відомо, яка саме конфігурація збирається. Вираз $<$<CONFIG:Debug>:значення> читається так: «якщо конфігурація Debug, то це значення, інакше нічого».

cmake_minimum_required(VERSION 3.10)
project(MyApp)

set(CMAKE_CXX_STANDARD 17)

add_executable(MyApp main.cpp)

# Загальні попередження залежно від компілятора
target_compile_options(MyApp PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
    $<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra>
    $<$<CXX_COMPILER_ID:Clang,AppleClang>:-Wall;-Wextra>
)

# Тільки для Debug
target_compile_definitions(MyApp PRIVATE
    $<$<CONFIG:Debug>:DEBUG_MODE>
)

target_compile_options(MyApp PRIVATE
    $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O0;-g>
)

# Тільки для Release
target_compile_options(MyApp PRIVATE
    $<$<AND:$<CONFIG:Release>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O3>
)

Кілька прапорців усередині виразу розділяються крапкою з комою (-Wall;-Wextra), бо вираз має повернути список. Складні умови будуються з $<AND:...>, $<OR:...>, $<NOT:...>.

Прапорці -DNDEBUG та -O3 для Release CMake додає сам. Ваша задача — додавати те, чого немає за замовчуванням: макроси проєкту, санітайзери, додаткові попередження.

Умова if(CMAKE_BUILD_TYPE ...)

Альтернатива виразам — звичайний if:

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Configuring for Debug")
    target_compile_definitions(MyApp PRIVATE DEBUG_LOGGING_ENABLED)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
    message(STATUS "Configuring for Release")
    target_compile_definitions(MyApp PRIVATE RELEASE_BUILD)
endif()

Цей варіант простіший для читання, але працює лише з генераторами однієї конфігурації. На Visual Studio CMAKE_BUILD_TYPE порожня, і жодна гілка не спрацює. Вирази-генератори від цього застраховані, тому для коду, який має працювати всюди, надавайте їм перевагу.

Макроси в коді C++

Макроси, визначені в CMakeLists.txt, доступні в коді:

#include <iostream>

int main() {
    std::cout << "Моя програма v1.0" << std::endl;

#ifdef DEBUG_MODE
    std::cout << "[DEBUG] Запущено в режимі відладки" << std::endl;
#else
    std::cout << "Release version" << std::endl;
#endif

#ifdef NDEBUG
    std::cout << "NDEBUG визначено: assert вимкнено" << std::endl;
#else
    std::cout << "NDEBUG не визначено: assert активний" << std::endl;
#endif

    return 0;
}

Ту саму програму зібрано в двох конфігураціях:

Debug і Release
$ cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug && cmake --build build-debug
$ ./build-debug/MyApp
Моя програма v1.0
[DEBUG] Запущено в режимі відладки
NDEBUG не визначено: assert активний
$ cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release && cmake --build build-release
$ ./build-release/MyApp
Моя програма v1.0
Release version
NDEBUG визначено: assert вимкнено

Окремі папки для різних конфігурацій

Для single-config генераторів одна папка збірки відповідає одній конфігурації. Тому для двох конфігурацій потрібні дві папки:

# Debug збірка
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug

# Release збірка
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release

Вони не заважають одна одній, і не потрібно перебудовувати проєкт при кожному перемиканні. Це зручно, коли пишете код у Debug, а час від часу перевіряєте швидкодію у Release.

Multi-config генератори

Visual Studio та Xcode створюють один проєкт, у якому всі конфігурації доступні одночасно. Тип обирається при збірці:

# Одна конфігурація проєкту, усі типи доступні
cmake -S . -B build -G "Visual Studio 17 2022"

cmake --build build --config Debug
cmake --build build --config Release
ГенераторКонфігурація задаєтьсяВиконуваний файл
Unix Makefiles, Ninja-DCMAKE_BUILD_TYPE=... при конфігураціїbuild/MyApp
Visual Studio, Xcode--config ... при збірціbuild/Debug/MyApp.exe, build/Release/MyApp.exe
Ninja Multi-Config--config ... при збірціbuild/Debug/MyApp, build/Release/MyApp

Саме через це в лекції про перший проєкт на Windows виконуваний файл лежав у підпапці Debug.

Змінні для прапорців конфігурацій

Прапорці кожної конфігурації зберігаються в змінних CMAKE_CXX_FLAGS_<КОНФІГУРАЦІЯ>:

# Додаткові прапорці для кожної конфігурації
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")

# Виведення поточних значень
message(STATUS "Compile flags: ${CMAKE_CXX_FLAGS}")
message(STATUS "Debug flags: ${CMAKE_CXX_FLAGS_DEBUG}")
message(STATUS "Release flags: ${CMAKE_CXX_FLAGS_RELEASE}")
Прямо присвоювати ці змінні в CMakeLists.txt замінює значення за замовчуванням і ламає перенесення проєкту на MSVC (прапорці -g там не діють). Для власних доповнень краще використовувати target_compile_options з виразами-генераторами, залишаючи стандартні значення на совісті CMake.

Різні бібліотеки для Debug і Release

Деякі зовнішні бібліотеки постачаються окремо для Debug та Release (наприклад, з суфіксом d). Вираз-генератор вибирає потрібну автоматично:

add_executable(MyApp main.cpp)

# Через if (тільки single-config)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_link_libraries(MyApp debug_lib)
else()
    target_link_libraries(MyApp release_lib)
endif()

# Через вирази-генератори (працює скрізь)
target_link_libraries(MyApp
    $<$<CONFIG:Debug>:debug_lib>
    $<$<CONFIG:Release>:release_lib>
)

Існує і старий синтаксис з ключовими словами debug та optimized у target_link_libraries, але вирази-генератори надійніші. Це одна з причин, чому пакетні менеджери (vcpkg) самі підбирають правильну версію бібліотеки для кожної конфігурації.

Питання для самоперевірки

Практичне завдання

Резюме

🧭 Чотири типи

Debug, Release, RelWithDebInfo і MinSizeRel. Обираються значенням CMAKE_BUILD_TYPE або параметром --config.

🚩 Реальні прапорці

Debug додає -g, Release -O3 -DNDEBUG. Саме NDEBUG вимикає assert() у всіх, крім Debug, конфігураціях.

🧮 Вирази-генератори

$<$<CONFIG:Debug>:...> обчислюється для кожної конфігурації й працює з усіма генераторами.

🗂️ Single і multi-config

Makefiles та Ninja: тип при конфігурації, окрема папка на кожен тип. Visual Studio та Xcode: усі типи в одному проєкті, вибір при збірці.
Далі розглянемо, як організувати великий проєкт: структуру папок, ієрархію CMakeLists.txt, власні модулі .cmake, попередньо скомпільовані заголовки та команду install.
Copyright © 2026