Команди CMake, пресети, скрипти збірки та кращі практики
Команди CMake, пресети, скрипти збірки та кращі практики
🎯 Мета лекції
- Користуватися основними командами
cmakeдля конфігурації, збірки, встановлення та діагностики кешу. - Автоматизувати збірку скриптом і описувати типові конфігурації через
CMakePresets.json. - Налаштувати
.gitignoreдля CMake-проєкту. - Застосовувати правила сучасного CMake: цілі, видимість, вирази-генератори, відмова від
file(GLOB).
🔑 Ключові терміни
- Пресет (preset): іменований набір налаштувань конфігурації у файлі
CMakePresets.json. compile_commands.json: база даних команд компіляції, яку читають clangd та інші інструменти.- Target-based підхід: налаштування прив'язуються до цілей, а не до всього проєкту.
Попередні лекції розкрили можливості CMake по одній. Ця лекція збирає практичний мінімум в одному місці: які команди вводити щодня, як автоматизувати збірку та які правила відрізняють сучасний CMakeLists.txt від застарілого.
Основні команди cmake
Генерація
# Базова генерація: джерела в поточній папці, збірка в build
cmake -S . -B build
# Вибір генератора
cmake -S . -B build -G "Visual Studio 17 2022"
cmake -S . -B build -G "Unix Makefiles"
cmake -S . -B build -G "Ninja"
# Передача змінної
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build -DENABLE_TESTS=ON
# Список доступних генераторів і довідка
cmake --help
Прапорець -G вказують лише при першій конфігурації папки build. Для зміни генератора потрібна нова папка або --fresh.
Збірка
# Збирати проєкт у вказаній папці
cmake --build build
# Паралельна збірка: 4 потоки
cmake --build build -j 4
# Автоматичний вибір кількості потоків (CMake 3.12+)
cmake --build build --parallel
# Конфігурація для multi-config генераторів
cmake --build build --config Release
# Зібрати конкретну ціль
cmake --build build --target MyApp
# Очистити результати збірки
cmake --build build --target clean
# Показати повні команди компіляції
cmake --build build --verbose
Прапорець --verbose корисний, коли потрібно побачити, які саме прапорці отримав компілятор: у ньому видно -O3, -DNDEBUG, шляхи -I та інші.
Встановлення
# Встановити проєкт
cmake --install build
# Встановити у власну папку
cmake --install build --prefix /usr/local
Налаштування та кеш
# Перегляд закешованих змінних
cmake -L -N build # основні, без повторної конфігурації
cmake -LA -N build # усі, разом з розширеними
cmake -LAH -N build # з описами
# Скинути кеш і конфігурувати з нуля, не видаляючи папку
cmake -S . -B build --fresh
# GUI-версія
cmake-gui .
Допоміжні команди
# Виконати файл як скрипт CMake (без проєкту)
cmake -P script.cmake
# Платформонезалежні файлові операції
cmake -E copy file.txt build/
cmake -E make_directory build/out
cmake -E echo "Привіт з cmake -E"
# Версія й довідка по конкретній команді
cmake --version
cmake --help-command add_executable
Режим cmake -E виконує прості файлові операції однаково на всіх платформах: це краще, ніж cp або copy у CMakeLists.txt, бо останні працюють лише на одній системі. Саме його використовували для copy_if_different у лекції про бібліотеки.
Корисні змінні CMake
Змінні, з якими працюватимете найчастіше:
| Група | Змінна | Значення |
|---|---|---|
| Шляхи | CMAKE_SOURCE_DIR | Корінь джерел |
CMAKE_BINARY_DIR | Корінь папки збірки | |
CMAKE_CURRENT_SOURCE_DIR | Папка поточного CMakeLists.txt | |
CMAKE_CURRENT_BINARY_DIR | Відповідна папка збірки | |
| Проєкт | PROJECT_NAME, PROJECT_VERSION, PROJECT_SOURCE_DIR | Назва, версія, корінь проєкту |
| Система | CMAKE_SYSTEM_NAME | Linux, Windows, Darwin |
WIN32, UNIX, APPLE | TRUE на відповідній платформі | |
| Компілятор | CMAKE_CXX_COMPILER, CMAKE_CXX_COMPILER_ID, CMAKE_CXX_COMPILER_VERSION | Шлях, виробник, версія |
MSVC | TRUE, якщо компілятор MSVC | |
| Збірка | CMAKE_BUILD_TYPE | Debug, Release, ... |
CMAKE_CXX_STANDARD | 11, 14, 17, 20, 23 |
LINUX. Вона з'явилась лише в CMake 3.25. Для сумісності зі старішими версіями використовуйте UNIX AND NOT APPLE.Налаштування .gitignore
Збірка в окремій папці робить .gitignore простим. Мінімум:
# Папки збірки
build/
build-*/
cmake-build-*/
out/
Якщо ви іноді запускаєте CMake всередині дерева джерел (чого робити не слід), або використовуєте IDE, додайте також службові файли:
# Службові файли CMake (на випадок in-source збірки)
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
CTestTestfile.cmake
install_manifest.txt
compile_commands.json
# Файли IDE
.vs/
.vscode/
.idea/
*.user
# Скомпільовані файли
*.exe
*.dll
*.so
*.dylib
*.a
*.lib
*.o
*.obj
.gitignore шаблони Makefile, *.sln або *.vcxproj, якщо вони можуть існувати у вашому репозиторії навмисно (наприклад, у сторонньому коді в external/). Загальне правило ігнорувати їх приховає потрібні файли. З out-of-source збіркою вистачає запису build/.У файл CMakeUserPresets.json розробники зберігають особисті налаштування (локальні шляхи, компілятори). Його додають до .gitignore, а спільний CMakePresets.json комітять у репозиторій.
Пресети: CMakePresets.json
Щоб не вводити щоразу довгі команди з прапорцями, налаштування зберігають у файлі CMakePresets.json (CMake 3.19 і новіше). Пресет — це іменований набір змінних, папки збірки та генератора.
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"displayName": "Release",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" },
{ "name": "release", "configurePreset": "release" }
]
}
Тепер замість довгих команд:
Пресети підтримують Visual Studio, CLion та VS Code, тому вся команда бачить однакові конфігурації. Для vcpkg у пресеті зручно задавати CMAKE_TOOLCHAIN_FILE, про що йтиметься в розділі про vcpkg.
compile_commands.json
Змінна CMAKE_EXPORT_COMPILE_COMMANDS=ON змушує CMake записати в папку збірки файл compile_commands.json. У ньому зберігається точна команда компіляції кожного .cpp. Саме цей файл читають clangd, clang-tidy та інші інструменти, щоб правильно підсвічувати код і знаходити заголовки:
[
{
"directory": ".../build/debug",
"command": "/usr/bin/c++ -g -std=gnu++17 -arch arm64 -o CMakeFiles/HelloCMake.dir/main.cpp.o -c .../main.cpp",
"file": ".../main.cpp"
}
]
Працює з генераторами Makefiles та Ninja. Для Visual Studio файл не створюється.
Скрипти для швидкої збірки
Якщо проєкт збирають часто, команди оформлюють у скрипт.
#!/bin/bash
# Кольори для виводу
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Параметри
BUILD_TYPE=${1:-Release}
BUILD_DIR="build"
echo -e "${YELLOW}Building project in ${BUILD_TYPE} mode...${NC}"
# Генерація
echo -e "${YELLOW}Running CMake...${NC}"
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" || {
echo -e "${RED}CMake configuration failed!${NC}"
exit 1
}
# Компіляція (--parallel сам вибирає кількість потоків)
echo -e "${YELLOW}Compiling...${NC}"
cmake --build "$BUILD_DIR" --parallel || {
echo -e "${RED}Build failed!${NC}"
exit 1
}
echo -e "${GREEN}Build successful!${NC}"
# Запустити тести, якщо вони є
if [ -f "$BUILD_DIR/CTestTestfile.cmake" ]; then
echo -e "${YELLOW}Running tests...${NC}"
ctest --test-dir "$BUILD_DIR" --output-on-failure
fi
Використання:
chmod +x build.sh
./build.sh # Release-збірка
./build.sh Debug # Debug-збірка
@echo off
setlocal
set BUILD_TYPE=%1
if "%BUILD_TYPE%"=="" set BUILD_TYPE=Release
set BUILD_DIR=build
echo Building project in %BUILD_TYPE% mode...
:: Генерація
echo Running CMake...
cmake -S . -B %BUILD_DIR% || goto :error
:: Компіляція
echo Compiling...
cmake --build %BUILD_DIR% --config %BUILD_TYPE% --parallel || goto :error
:: Успіх
echo Build successful!
:: Запустити тести, якщо вони є
if exist %BUILD_DIR%\CTestTestfile.cmake (
echo Running tests...
ctest --test-dir %BUILD_DIR% -C %BUILD_TYPE% --output-on-failure
)
goto :end
:error
echo Build failed!
exit /b 1
:end
Відмінності від типових прикладів з інтернету: тип збірки передається як -DCMAKE_BUILD_TYPE лише на Linux/macOS, а в .bat на Windows як --config (multi-config генератор Visual Studio). Команда nproc замінена на --parallel, бо nproc на macOS немає. Скрипт build.sh протестовано з обома типами збірки:
Кращі практики
Окремі папки збірки
# Правильно
cmake -S . -B build
cmake --build build
# Неправильно: засмічує вихідний код
cmake .
make
Реальна мінімальна версія
# Вказуйте версію, яка справді потрібна
cmake_minimum_required(VERSION 3.15)
# Діапазон: мінімум 3.10, але нові політики до 3.25
cmake_minimum_required(VERSION 3.10...3.25)
Версія має відповідати вашим командам. Якщо ви використовуєте FetchContent_MakeAvailable, потрібен CMake 3.14, target_precompile_headers — 3.16, cmake --preset — 3.19.
Target-based підхід
# Сучасний CMake: налаштування прив'язані до цілі
target_include_directories(MyApp PRIVATE ${SOME_INCLUDE})
target_compile_definitions(MyApp PRIVATE DEBUG=1)
target_link_libraries(MyApp PRIVATE SomeLib)
# Застарілий стиль: налаштування діють на ВСІ цілі проєкту
include_directories(${SOME_INCLUDE})
add_definitions(-DDEBUG=1)
link_libraries(SomeLib)
Застарілі команди змінюють усі цілі в папці та підпапках. З ростом проєкту це приводить до несподіваних залежностей: бібліотека, якій шлях не потрібен, раптом його отримала. Цільові команди (target_*) обмежують дію однією ціллю і за допомогою PUBLIC/PRIVATE/INTERFACE точно описують, хто що бачить.
Явна видимість
# PUBLIC: потрібно і цій цілі, і тим, хто її використовує
# PRIVATE: потрібно лише цій цілі
# INTERFACE: потрібно лише тим, хто її використовує
add_library(MyLib src.cpp)
target_include_directories(MyLib
PUBLIC include/ # користувач MyLib бачить include/
PRIVATE src/internal/ # лише MyLib бачить src/internal/
)
Правило: якщо в публічних заголовках є #include <щось>, залежність має бути PUBLIC. Якщо вона потрібна лише в .cpp, достатньо PRIVATE.
Явний перелік файлів замість GLOB
# Ризиковано: нові файли без повторної конфігурації не потрапляють у збірку
file(GLOB SOURCES "src/*.cpp")
add_executable(MyApp ${SOURCES})
# Надійно
set(SOURCES
src/main.cpp
src/app.cpp
src/utils.cpp
)
add_executable(MyApp ${SOURCES})
Вирази-генератори замість розгалужень за типом збірки
target_compile_options(MyApp PRIVATE
$<$<CONFIG:Debug>:-g;-O0>
$<$<CONFIG:Release>:-O3>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<CXX_COMPILER_ID:GNU>:-Wall>
)
Вирази працюють з усіма генераторами, а умова за CMAKE_BUILD_TYPE лише з однією конфігурацією.
Додаткові правила
- Не змінюйте
CMAKE_CXX_FLAGSнапряму у бібліотеках: прапорці потрапляють у всіх, хто підключає ваш проєкт. Використовуйтеtarget_compile_options. - Вимагайте стандарт від цілі, а не лише глобально:
target_compile_features(MyLib PUBLIC cxx_std_17)передає вимогу користувачам бібліотеки. - Використовуйте псевдоніми
Проєкт::Цільдля власних бібліотек. - Вмикайте
CMAKE_EXPORT_COMPILE_COMMANDS, щоб редактор коду підказував правильно. - Тримайте
CMakePresets.jsonу репозиторії, а особисті налаштування вCMakeUserPresets.json.
Питання для самоперевірки
cmake --build build -j 2. Прапорець -j передається системі збірки, яка стоїть за генератором (Makefiles, Ninja, MSBuild). Команда однакова на всіх платформах.target_include_directories обмежує дію конкретною ціллю й видимістю PRIVATE, PUBLIC або INTERFACE.Практичне завдання
Для будь-якого вашого CMake-проєкту:
- Створіть
CMakePresets.jsonіз двома пресетами:debug(з тестами) таrelease(без тестів). - Увімкніть
CMAKE_EXPORT_COMPILE_COMMANDSдля Debug і перевірте, що файлcompile_commands.jsonз'явився в папці збірки. - Складіть
.gitignore, який ігноруєbuild/та файли IDE. - Перепишіть
file(GLOB)на явний перелік, якщо він є в проєкті. - Замініть
include_directoriesтаadd_definitions(якщо є) наtarget_*версії.
cmake --preset debug та cmake --build --preset debug збирають проєкт без додаткових аргументів, cmake --preset release створює окрему папку build/release, а в репозиторій не потрапляє жодного файлу з папки build/.Резюме
⌨️ Команди
cmake -S -B конфігурує, cmake --build збирає, cmake --install встановлює, -L і --fresh керують кешем.📋 Пресети
CMakePresets.json замінює довгі команди. IDE та CLI читають одні й ті самі налаштування.🧰 Інструменти
compile_commands.json для clangd, скрипти збірки, .gitignore з build/.✨ Сучасний CMake
Тестування з CMake: CTest, Google Test, покриття коду
Вбудована система тестування CTest, add_test, запуск і фільтрація тестів, інтеграція Google Test через FetchContent та gtest_discover_tests, пастка assert у Release, вимірювання покриття коду.
Практичні проєкти: калькулятор, Qt-застосунок і кросплатформна збірка
Три завершені проєкти на CMake: консольний калькулятор із бібліотекою і тестами, графічний застосунок на Qt6, кросплатформна збірка для Raspberry Pi через toolchain-файл, а також шаблон нового проєкту.