CMake і vcpkg

Команди CMake, пресети, скрипти збірки та кращі практики

Довідник команд cmake, корисні змінні, .gitignore для CMake-проєктів, скрипти збірки для Linux/macOS і Windows, CMakePresets.json, compile_commands.json та шість правил сучасного 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_NAMELinux, Windows, Darwin
WIN32, UNIX, APPLETRUE на відповідній платформі
КомпіляторCMAKE_CXX_COMPILER, CMAKE_CXX_COMPILER_ID, CMAKE_CXX_COMPILER_VERSIONШлях, виробник, версія
MSVCTRUE, якщо компілятор MSVC
ЗбіркаCMAKE_BUILD_TYPEDebug, Release, ...
CMAKE_CXX_STANDARD11, 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" }
  ]
}

Тепер замість довгих команд:

робота з пресетами
$ cmake --list-presets
Available configure presets:
"debug" - Debug
"release" - Release
$ cmake --preset debug
-- Configuring done (0.7s)
-- Generating done (0.0s)
-- Build files have been written to: .../build/debug
$ cmake --build --preset debug
[100%] Linking CXX executable HelloCMake
[100%] Built target HelloCMake

Пресети підтримують 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-збірка

Відмінності від типових прикладів з інтернету: тип збірки передається як -DCMAKE_BUILD_TYPE лише на Linux/macOS, а в .bat на Windows як --config (multi-config генератор Visual Studio). Команда nproc замінена на --parallel, бо nproc на macOS немає. Скрипт build.sh протестовано з обома типами збірки:

./build.sh Debug
$ ./build.sh Debug
Building project in Debug mode...
Running CMake...
-- Configuring done (0.5s)
Compiling...
[ 50%] Building CXX object CMakeFiles/HelloCMake.dir/main.cpp.o
[100%] Linking CXX executable HelloCMake
[100%] Built target HelloCMake
Build successful!

Кращі практики

Окремі папки збірки

# Правильно
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 -S -B конфігурує, cmake --build збирає, cmake --install встановлює, -L і --fresh керують кешем.

📋 Пресети

CMakePresets.json замінює довгі команди. IDE та CLI читають одні й ті самі налаштування.

🧰 Інструменти

compile_commands.json для clangd, скрипти збірки, .gitignore з build/.

✨ Сучасний CMake

Цілі замість глобальних налаштувань, явна видимість, явні списки файлів, вирази-генератори.
Усе вивчене застосуємо на трьох завершених проєктах: консольному калькуляторі, графічній програмі на Qt та збірці для іншої архітектури (Raspberry Pi).
Copyright © 2026