Практичні проєкти: калькулятор, Qt-застосунок і кросплатформна збірка
Практичні проєкти: калькулятор, Qt-застосунок і кросплатформна збірка
🎯 Мета лекції
- Зібрати повний проєкт із бібліотекою, програмою та тестами за усталеною структурою.
- Підключити великий фреймворк (Qt6) через
find_packageі налаштувати автоматичні генератори коду. - Налаштувати кросплатформну збірку для іншої архітектури через toolchain-файл.
- Створювати нові проєкти за перевіреним шаблоном.
🔑 Ключові терміни
- Toolchain-файл: файл CMake, який описує, яким компілятором і для якої системи збирати.
- Кросплатформна збірка (cross-compilation): збірка на одній платформі програми для іншої.
- AUTOMOC/AUTOUIC/AUTORCC: вбудовані в CMake генератори коду для Qt.
- Цільова платформа (target platform): система, на якій програма запускатиметься.
У попередніх лекціях кожна можливість CMake розглядалася окремо. Нижче вони зібрані у трьох проєктах, які відображають типові сценарії: консольна програма з бібліотекою й тестами, застосунок із графічним фреймворком і збірка для іншого типу процесора.
Проєкт 1: консольний калькулятор
Калькулятор складається з бібліотеки calculator_lib (арифметика та движок із історією обчислень), програми з меню та двох тестових програм. Бібліотека відокремлена від main.cpp, тому тести працюють із нею напряму, без діалогу з користувачем.
Структура
Як влаштовано проєкт
- Бібліотека
calculator_libмістить усю логіку: функції арифметики та класEngine. Заголовки лежать уinclude/calculator/, тому в коді пишемо#include "calculator/operations.h", і папкаcalculator/працює як простір імен для файлів. target_include_directories(... PUBLIC ...)зBUILD_INTERFACEіINSTALL_INTERFACEпередає шлях до заголовків програмі й тестам автоматично.- Програма
calculatorлінкується з бібліотекою черезPRIVATE: користувачів у програми немає, тому передавати залежність далі немає сенсу. - Тести створюються циклом
foreach: кожен файл стає окремою програмою і окремим тестом CTest. - Перевірки
CHECKнаписані власним макросом, а не черезassert. Як з'ясували в лекції про тестування,assertу Release-збірці зникає, аCHECKпрацює завжди.
Збірка, тести, запуск
Якщо подати програмі послідовність дій (4, 10, 0 — ділення на нуль; 5, 2, 10 — степінь; 6, 81 — корінь; 0 — вихід), у виводі з'являються такі рядки:
CHECK замість assert дає однаковий результат у кожній конфігурації: тести можна запускати і в Release (-DCMAKE_BUILD_TYPE=Release), і в Debug. Debug-збірка потрібна для відладки, а не для того, щоб тести взагалі щось перевіряли.Проєкт 2: застосунок із графічним інтерфейсом (Qt)
Qt — найпоширеніший C++-фреймворк для графічних програм, що працює на Windows, Linux і macOS. Він має власні інструменти генерації коду, і CMake вміє запускати їх автоматично. Це приклад проєкту із великою зовнішньою залежністю, яка підключається через find_package.
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.
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.
project(). Тому параметр -DCMAKE_TOOLCHAIN_FILE задається при першому запуску cmake для папки збірки. Змінити його в уже налаштованій папці не вийде: створіть нову.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 конфігурація проходить і програма друкує Hello from MyNewProject!.
Питання для самоперевірки
main.cpp як бібліотеку: у ньому вже є функція main, яка вступила б у конфлікт із тестовим main. Бібліотека без main лінкується і з програмою, і з тестами. Ще одна перевага: логіку можна тестувати без введення даних із клавіатури.moc (Meta-Object Compiler) для класів із макросом Q_OBJECT. Без CMAKE_AUTOMOC довелося б запускати moc вручну для кожного такого заголовка. З нею CMake знаходить потрібні класи й підключає згенерований код автоматично.python чи protoc, виконуються під час збірки, на вашому комп'ютері, тож потрібні версії для хоста. Бібліотеки й заголовки лінкуються в результуючий файл, який запуститься на Raspberry Pi, тому вони мають бути саме для ARM. Якщо їх сплутати, зберуться бінарні файли для неправильної архітектури.qemu-arm), який можна передати CMake через CMAKE_CROSSCOMPILING_EMULATOR.Практичне завдання
- Додайте в
EngineметодclearHistory()та тест для нього. - Виведіть історію обчислень у
main.cppперед виходом, використовуючиEngineзамість безпосередніх викликівadd,subtractтощо. - Додайте опцію
BUILD_SHARED_LIBSі перевірте, що проєкт збирається і як статична, і як динамічна бібліотека. - Додайте команду
installдля програми, бібліотеки та заголовків, виконайтеcmake --install build --prefix outі подивіться на вміст папкиout.
main.cpp створіть один екземпляр Calculator::Engine engine;, замість окремих add(a, b) викликайте engine.apply('+', a, b, result), а перед виходом пройдіться по engine.history() циклом for (const auto& line : engine.history()). Залежність програми від calculator_lib не змінюється: Engine вже у бібліотеці.Резюме
🧮 Консольний проєкт
CHECK працюють в усіх конфігураціях, на відміну від assert.🖼️ Qt-застосунок
find_package(Qt6), прапорці AUTOMOC/AUTOUIC/AUTORCC, WIN32 і MACOSX_BUNDLE, розгортання через windeployqt.🔧 Кросплатформна збірка
📦 Шаблон проєкту
CMakeLists.txt, main.cpp, .gitignore і збирає перший запуск. Файл tests/CMakeLists.txt обов'язковий.npm або pip у інших мовах.Команди CMake, пресети, скрипти збірки та кращі практики
Довідник команд cmake, корисні змінні, .gitignore для CMake-проєктів, скрипти збірки для Linux/macOS і Windows, CMakePresets.json, compile_commands.json та шість правил сучасного CMake.
Пакетні менеджери для C++: vcpkg, Conan, CPM та системні пакети
Навіщо C++ потрібні пакетні менеджери, порівняння vcpkg, Conan, CPM, Hunter і системних менеджерів, базове використання Conan 2, вибір інструмента для курсу.