CMake і vcpkg

Практичні проєкти: калькулятор, Qt-застосунок і кросплатформна збірка

Три завершені проєкти на CMake: консольний калькулятор із бібліотекою і тестами, графічний застосунок на Qt6, кросплатформна збірка для Raspberry Pi через toolchain-файл, а також шаблон нового проєкту.

Практичні проєкти: калькулятор, Qt-застосунок і кросплатформна збірка

🎯 Мета лекції

  • Зібрати повний проєкт із бібліотекою, програмою та тестами за усталеною структурою.
  • Підключити великий фреймворк (Qt6) через find_package і налаштувати автоматичні генератори коду.
  • Налаштувати кросплатформну збірку для іншої архітектури через toolchain-файл.
  • Створювати нові проєкти за перевіреним шаблоном.

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

  • Toolchain-файл: файл CMake, який описує, яким компілятором і для якої системи збирати.
  • Кросплатформна збірка (cross-compilation): збірка на одній платформі програми для іншої.
  • AUTOMOC/AUTOUIC/AUTORCC: вбудовані в CMake генератори коду для Qt.
  • Цільова платформа (target platform): система, на якій програма запускатиметься.

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

Проєкт 1: консольний калькулятор

Калькулятор складається з бібліотеки calculator_lib (арифметика та движок із історією обчислень), програми з меню та двох тестових програм. Бібліотека відокремлена від main.cpp, тому тести працюють із нею напряму, без діалогу з користувачем.

Структура

Як влаштовано проєкт

  1. Бібліотека calculator_lib містить усю логіку: функції арифметики та клас Engine. Заголовки лежать у include/calculator/, тому в коді пишемо #include "calculator/operations.h", і папка calculator/ працює як простір імен для файлів.
  2. target_include_directories(... PUBLIC ...) з BUILD_INTERFACE і INSTALL_INTERFACE передає шлях до заголовків програмі й тестам автоматично.
  3. Програма calculator лінкується з бібліотекою через PRIVATE: користувачів у програми немає, тому передавати залежність далі немає сенсу.
  4. Тести створюються циклом foreach: кожен файл стає окремою програмою і окремим тестом CTest.
  5. Перевірки CHECK написані власним макросом, а не через assert. Як з'ясували в лекції про тестування, assert у Release-збірці зникає, а CHECK працює завжди.

Збірка, тести, запуск

збірка і тести
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
-- ========================================
-- Calculator v1.0.0
-- ========================================
-- Build type: Release
-- Build tests: ON
-- ========================================
$ cmake --build build
[ 33%] Linking CXX static library libcalculator_lib.a
[ 33%] Built target calculator_lib
[ 55%] Linking CXX executable calculator
[ 55%] Built target calculator
[ 77%] Linking CXX executable test_operations
[ 77%] Built target test_operations
[100%] Linking CXX executable test_calculator
[100%] Built target test_calculator
$ cd build && ctest
1/2 Test #1: test_operations .................. Passed 0.31 sec
2/2 Test #2: test_calculator .................. Passed 0.17 sec
100% tests passed, 0 tests failed out of 2

Якщо подати програмі послідовність дій (4, 10, 0 — ділення на нуль; 5, 2, 10 — степінь; 6, 81 — корінь; 0 — вихід), у виводі з'являються такі рядки:

./build/calculator
Виберіть операцію: Введіть перше число: Введіть друге число: Помилка: ділення на нуль!
Виберіть операцію: Введіть перше число: Введіть друге число: Результат: 2 ^ 10 = 1024
Виберіть операцію: Введіть число: Результат: √81 = 9
Виберіть операцію: До побачення!
Використання CHECK замість assert дає однаковий результат у кожній конфігурації: тести можна запускати і в Release (-DCMAKE_BUILD_TYPE=Release), і в Debug. Debug-збірка потрібна для відладки, а не для того, щоб тести взагалі щось перевіряли.

Проєкт 2: застосунок із графічним інтерфейсом (Qt)

Qt — найпоширеніший C++-фреймворк для графічних програм, що працює на Windows, Linux і macOS. Він має власні інструменти генерації коду, і CMake вміє запускати їх автоматично. Це приклад проєкту із великою зовнішньою залежністю, яка підключається через find_package.

Qt встановлюється окремо: офіційним інсталятором Qt, через brew install qt на macOS або apt install qt6-base-dev на Ubuntu. Також доступний пакет qtbase у vcpkg, але його збірка триває дуже довго. Після встановлення CMake має знайти Qt: якщо ні, вкажіть -DCMAKE_PREFIX_PATH=/шлях/до/Qt/6.x/платформа.

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
project(QtApp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Автоматичні генератори коду Qt
set(CMAKE_AUTOMOC ON)  # Meta-Object Compiler: сигнали та слоти
set(CMAKE_AUTORCC ON)  # Resource Compiler: файли ресурсів .qrc
set(CMAKE_AUTOUIC ON)  # UI Compiler: файли форм .ui

# Знайти Qt
find_package(Qt6 REQUIRED COMPONENTS Widgets)

# Вихідні файли
set(SOURCES
    src/main.cpp
    src/mainwindow.cpp
    src/mainwindow.h
)

# Створення виконуваного файлу з урахуванням платформи
if(WIN32)
    add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
elseif(APPLE)
    add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SOURCES})
else()
    add_executable(${PROJECT_NAME} ${SOURCES})
endif()

# Зв'язати з Qt
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)

# Встановлення
install(TARGETS ${PROJECT_NAME}
    BUNDLE DESTINATION .
    RUNTIME DESTINATION bin
)

Що тут нового

  • CMAKE_AUTOMOC. Механізм сигналів і слотів Qt потребує додаткового коду, який створює спеціальний компілятор moc. З цією змінною CMake сам знаходить класи з макросом Q_OBJECT у заголовках і запускає moc перед компіляцією.
  • CMAKE_AUTOUIC перетворює файли форм .ui (створені в Qt Designer) на C++-заголовки, CMAKE_AUTORCC вбудовує ресурси .qrc (зображення, переклади) у виконуваний файл.
  • find_package(Qt6 REQUIRED COMPONENTS Widgets) знаходить лише потрібну частину Qt. Ціль Qt6::Widgets уже залежить від Qt6::Gui та Qt6::Core, тому перелічувати їх окремо не потрібно.
  • WIN32 та MACOSX_BUNDLE — прапорці add_executable. Перший прибирає консольне вікно на Windows, другий створює на macOS пакет .app замість звичайного виконуваного файлу.

Мінімальний код застосунку

Розгортання на Windows

Програма Qt на Windows не запуститься на чужому комп'ютері без бібліотек Qt (Qt6Core.dll, Qt6Widgets.dll і плагінів платформи). Збирати їх вручну не потрібно: Qt постачає утиліту windeployqt, яку можна запустити після збірки:

if(WIN32)
    find_program(WINDEPLOYQT windeployqt HINTS "${Qt6_DIR}/../../../bin")

    if(WINDEPLOYQT)
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
            COMMAND ${WINDEPLOYQT} --no-translations
                    $<TARGET_FILE:${PROJECT_NAME}>
            COMMENT "Копіювання бібліотек Qt поруч з виконуваним файлом"
        )
    endif()
endif()

windeployqt аналізує виконуваний файл, визначає потрібні DLL і плагіни та копіює їх поруч. Подібні утиліти є й на інших платформах: macdeployqt для macOS і linuxdeploy для Linux.

У старих прикладах для цієї мети DLL копіюють вручну командою copy_if_different, пошук Qt6::qmake використовують, щоб отримати папку Qt. Такий підхід ламається, коли змінюється склад компонентів або встановлюється нова версія Qt. windeployqt відстежує залежності автоматично.

Проєкт 3: кросплатформна збірка для Raspberry Pi

До цього часу збірка виконувалась на тій же системі, на якій програма запускається. Часто це неможливо: мікроконтролер або Raspberry Pi надто слабкі для компіляції, і програму збирають на потужному ПК. Така збірка називається кросплатформною (cross-compilation). Компілятор працює на хост-системі (ваш комп'ютер), а результат призначений для цільової системи (Raspberry Pi).

Щоб CMake знав, що збирати потрібно для іншої системи, її описують у файлі toolchain.

Toolchain-файл

# toolchain-raspberry.cmake

# Цільова система: Linux на процесорі ARM
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Компілятори для ARM
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# Де шукати бібліотеки та заголовки цільової системи
set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)

# Програми (cmake, python) шукати на хості, бібліотеки й заголовки в цільовому sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Рядки по порядку:

  • CMAKE_SYSTEM_NAME переводить CMake в режим кросплатформної збірки. Після цього змінна CMAKE_CROSSCOMPILING стає істинною.
  • CMAKE_SYSTEM_PROCESSOR описує архітектуру процесора. У прикладі arm відповідає 32-бітній Raspberry Pi OS. Для 64-бітної системи використовують aarch64 і компілятор aarch64-linux-gnu-g++.
  • CMAKE_C_COMPILER, CMAKE_CXX_COMPILER вказують кросплатформні компілятори. Їх ставлять на хості: наприклад, sudo apt install g++-arm-linux-gnueabihf на Ubuntu.
  • CMAKE_FIND_ROOT_PATH — корінь файлової системи цільової платформи (sysroot). find_package, find_library шукають бібліотеки саме там, а не серед бібліотек хоста.
  • Режими MODE_PROGRAM/LIBRARY/INCLUDE. Програми (наприклад, python або protoc) виконуються на хості, тому їх шукають у системі хоста (NEVER). А бібліотеки й заголовки потрібні для цільової платформи, тому їх шукають тільки в sysroot (ONLY).

Використання

cmake -S . -B build-rpi -DCMAKE_TOOLCHAIN_FILE=toolchain-raspberry.cmake
cmake --build build-rpi

Перевірити, що результат справді призначений для ARM, можна командою file:

file build-rpi/MyApp
# build-rpi/MyApp: ELF 32-bit LSB executable, ARM, EABI5 ...

Запускати цей файл на ПК не вийде. Його копіюють на Raspberry Pi (наприклад, scp build-rpi/MyApp pi@raspberrypi:~) й запускають там. Для швидкої перевірки без пристрою CMake підтримує емулятор: якщо вказати -DCMAKE_CROSSCOMPILING_EMULATOR=qemu-arm, команди add_test і CTest запускатимуть тести через qemu-arm.

Toolchain-файл читається на самому початку конфігурації, до команди project(). Тому параметр -DCMAKE_TOOLCHAIN_FILE задається при першому запуску cmake для папки збірки. Змінити його в уже налаштованій папці не вийде: створіть нову.
Менеджер vcpkg також вміє збирати бібліотеки для іншої платформи: для цього використовується власний triplet (наприклад, arm-linux), а його toolchain vcpkg.cmake підключає ваш toolchain-файл через змінну VCPKG_CHAINLOAD_TOOLCHAIN_FILE. Це детально розглянуто у розділі про vcpkg.

Шаблон нового проєкту

Щоб не повторювати кожного разу структуру з нуля, використовуйте перевірений скрипт створення мінімального проєкту:

# Створити папку проєкту
mkdir MyNewProject && cd MyNewProject

# Базова структура
mkdir -p src include tests docs scripts

# Головний CMakeLists.txt
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.15)
project(MyNewProject VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(BUILD_TESTS "Build tests" ON)

add_executable(${PROJECT_NAME} src/main.cpp)

if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()
EOF

# Файл тестів (без нього add_subdirectory(tests) завершиться помилкою)
echo "# Тести проєкту додаються тут" > tests/CMakeLists.txt

# Головний файл
cat > src/main.cpp << 'EOF'
#include <iostream>

int main() {
    std::cout << "Hello from MyNewProject!" << std::endl;
    return 0;
}
EOF

# .gitignore
cat > .gitignore << 'EOF'
build/
build-*/
*.user
.vs/
.vscode/
.idea/
EOF

# Готово! Збираємо
cmake -S . -B build
cmake --build build
./build/MyNewProject
У поширених версіях цього шаблону немає рядка зі створенням tests/CMakeLists.txt, але опція BUILD_TESTS увімкнена за замовчуванням. Тоді add_subdirectory(tests) падає з помилкою «The source directory ... does not contain a CMakeLists.txt». Саме цей рядок тут додано, а наслідки його відсутності можна побачити нижче.
шаблон без tests/CMakeLists.txt
$ cmake -S . -B build
CMake Error at CMakeLists.txt:13 (add_subdirectory):
The source directory
/.../MyNewProject/tests
does not contain a CMakeLists.txt file.

Після створення tests/CMakeLists.txt конфігурація проходить і програма друкує Hello from MyNewProject!.

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

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

Резюме

🧮 Консольний проєкт

Бібліотека з логікою, програма і тести окремо. Перевірки через CHECK працюють в усіх конфігураціях, на відміну від assert.

🖼️ Qt-застосунок

find_package(Qt6), прапорці AUTOMOC/AUTOUIC/AUTORCC, WIN32 і MACOSX_BUNDLE, розгортання через windeployqt.

🔧 Кросплатформна збірка

Toolchain-файл задає систему, компілятор і правила пошуку: програми на хості, бібліотеки в sysroot.

📦 Шаблон проєкту

Скрипт створює структуру папок, CMakeLists.txt, main.cpp, .gitignore і збирає перший запуск. Файл tests/CMakeLists.txt обов'язковий.
Цим завершується перша частина розділу, присвячена самому CMake. Далі розглянемо, звідки брати бібліотеки: пакетні менеджери, насамперед vcpkg, який вирішує задачу підключення залежностей так, як npm або pip у інших мовах.
Copyright © 2026