CMake і vcpkg

Синтаксис CMakeLists.txt: команди, змінні, списки

Основні команди CMake (cmake_minimum_required, project, set, add_executable, add_library, target_link_libraries, message), вбудовані й власні змінні, списки, коментарі та повний приклад конфігурації.

Синтаксис CMakeLists.txt: команди, змінні, списки

🎯 Мета лекції

  • Читати й писати команди CMake: cmake_minimum_required, project, set, add_executable, add_library, target_link_libraries, message.
  • Користуватися вбудованими змінними PROJECT_*, CMAKE_* та власними змінними.
  • Працювати зі списками і правильно розрізняти значення та ім'я змінної.
  • Скласти повний базовий CMakeLists.txt із попередженнями компілятора та діагностикою.

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

  • Команда (command): виклик на кшталт set(...); регістр імені команди не має значення.
  • Змінна (variable): іменоване значення, яке читається через ${ІМ'Я}.
  • Список (list): рядок, у якому елементи розділені крапкою з комою.
  • Ціль (target): виконуваний файл або бібліотека, до якої прив'язуються налаштування.

Минулої лекції весь CMakeLists.txt складався з чотирьох команд. Щоб писати власні проєкти, потрібно знати, що саме робить кожна з них, які параметри приймає і які змінні CMake створює сам.

Загальні правила мови CMake

CMakeLists.txt — це програма на мові CMake. Вона виконується зверху вниз під час конфігурації. Мова проста і має кілька правил, які варто засвоїти одразу:

  • Команда записується як ім'я(аргумент аргумент ...). Аргументи розділяються пробілами або переносами рядків.
  • Імена команд нечутливі до регістру: add_executable і ADD_EXECUTABLE однакові. У сучасному коді прийнято писати малими літерами.
  • Імена змінних чутливі до регістру: MyVar і MYVAR різні змінні.
  • Значення — завжди рядки. Число 42 і слово hello зберігаються однаково. Інтерпретує їх команда, яка читає змінну.
  • Лапки потрібні, якщо значення містить пробіл або порожнє: set(TITLE "Hello CMake").

Основні команди

cmake_minimum_required: мінімальна версія

cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.10...3.25)  # діапазон версій

Різні версії CMake мають різні можливості та різну поведінку. Команда дає два гарантії: проєкт не збиратиметься занадто старим CMake, а CMake вмикає нові політики поведінки (policies) до зазначеної версії. Діапазон 3.10...3.25 означає: вимагати щонайменше 3.10, але якщо встановлено 3.25, застосувати політики 3.25.

Команду cmake_minimum_required слід викликати першою, до project(). Якщо її пропустити, CMake виведе попередження, а політики залишаться в застарілому режимі.

project: опис проєкту

# Базовий варіант
project(MyProject)

# З версією
project(MyProject VERSION 1.0.0)

# З мовами програмування
project(MyProject LANGUAGES CXX C)

# Повний варіант
project(MyProject
    VERSION 1.2.3
    DESCRIPTION "My awesome project"
    LANGUAGES CXX
)

Команда робить три речі: задає ім'я проєкту, розбирає версію на складові і визначає мови, для яких CMake шукатиме компілятори. Якщо LANGUAGES не вказано, CMake шукає і C, і C++ (у виводі конфігурації з лекції про перший проєкт були рядки і про C, і про C++). Запис LANGUAGES CXX прискорює конфігурацію.

Після виклику з'являються змінні:

ЗміннаЗначення для project(MyProject VERSION 1.2.3 ...)
${PROJECT_NAME}MyProject
${PROJECT_VERSION}1.2.3
${PROJECT_VERSION_MAJOR}1
${PROJECT_VERSION_MINOR}2
${PROJECT_VERSION_PATCH}3
${PROJECT_SOURCE_DIR}Абсолютний шлях до папки з CMakeLists.txt, де викликано project()

set: встановлення змінних

# Стандарт C++
set(CMAKE_CXX_STANDARD 17)

# Власні змінні
set(MY_VARIABLE "Hello")
set(MY_NUMBER 42)

# Список файлів
set(SOURCES
    main.cpp
    utils.cpp
    helper.cpp
)

# Використання змінної
add_executable(MyApp ${SOURCES})

Команда set(ІМ'Я значення...) записує значення в змінну. Якщо значень кілька, вони утворюють список. Прочитати змінну можна записом ${ІМ'Я}: CMake підставить значення перед виконанням команди.

add_executable: створення програми

# Один файл
add_executable(MyApp main.cpp)

# Кілька файлів
add_executable(MyApp main.cpp utils.cpp helper.cpp)

# Через змінну
set(SOURCES main.cpp utils.cpp helper.cpp)
add_executable(MyApp ${SOURCES})

# Ім'я цілі може містити дефіси
add_executable(my-cool-app main.cpp)  # my-cool-app або my-cool-app.exe

Перший аргумент — ім'я цілі. Платформа додає розширення сама: .exe на Windows і нічого на Linux та macOS. Ім'я цілі має бути унікальним у межах усього проєкту.

add_library: створення бібліотеки

# Статична бібліотека (.lib або .a)
add_library(MyLib STATIC lib.cpp lib.h)

# Динамічна бібліотека (.dll, .so, .dylib)
add_library(MyLib SHARED lib.cpp lib.h)

# Бібліотека тільки із заголовків (header-only)
add_library(MyLib INTERFACE)

Три типи бібліотек докладно розібрано в лекції про бібліотеки. Тут важливо, що add_library створює ціль так само, як add_executable, лише без main().

add_executable(MyApp main.cpp)
add_library(MyLib lib.cpp)
target_link_libraries(MyApp MyLib)

Команда повідомляє, що MyApp використовує MyLib. CMake подбає про порядок збірки (спершу бібліотека, потім програма) і передасть програмі все, що потрібно для компіляції: шляхи до заголовків та прапорці.

message: діагностика

message("Hello from CMake!")
message(STATUS "Configuring project...")
message(WARNING "This is a warning")
message(FATAL_ERROR "This stops CMake")

# Вивести значення змінних
message(STATUS "Project name: ${PROJECT_NAME}")
message(STATUS "C++ standard: ${CMAKE_CXX_STANDARD}")
РежимЩо відбувається
(без режиму)Звичайний текст у потік помилок
STATUSПовідомлення з префіксом --, виводиться поряд зі службовими рядками
WARNINGПопередження, конфігурація триває
FATAL_ERRORПомилка, конфігурація зупиняється, збірка не створюється
message(STATUS ...) — основний інструмент налагодження CMakeLists.txt. Якщо не зрозуміло, чому умова не спрацьовує, виведіть значення змінної й подивіться на реальне значення.

Змінні в CMake

Вбудовані змінні

CMake сам визначає багато змінних. Їх можна читати, а деякі й змінювати.

# Інформація про проєкт
${PROJECT_NAME}              # Назва проєкту
${PROJECT_VERSION}           # Версія проєкту
${PROJECT_SOURCE_DIR}        # Папка з CMakeLists.txt, де викликано project()
${PROJECT_BINARY_DIR}        # Відповідна папка збірки

# Інформація про систему
${CMAKE_SYSTEM_NAME}         # Linux, Windows, Darwin (macOS)
${CMAKE_SYSTEM_VERSION}      # Версія ОС, для якої збираємо
${CMAKE_HOST_SYSTEM_NAME}    # ОС, на якій запущено CMake

# Інформація про компілятор
${CMAKE_CXX_COMPILER}        # Шлях до компілятора C++
${CMAKE_CXX_COMPILER_ID}     # GNU, Clang, AppleClang, MSVC
${CMAKE_CXX_COMPILER_VERSION}# Версія компілятора

# Шляхи
${CMAKE_CURRENT_SOURCE_DIR}  # Папка поточного CMakeLists.txt
${CMAKE_CURRENT_BINARY_DIR}  # Папка збірки для поточного CMakeLists.txt
${CMAKE_SOURCE_DIR}          # Корінь проєкту (найвищий CMakeLists.txt)
${CMAKE_BINARY_DIR}          # Корінь папки збірки

# Конфігурація
${CMAKE_BUILD_TYPE}          # Debug, Release, RelWithDebInfo, MinSizeRel

Різницю між CMAKE_SOURCE_DIR та CMAKE_CURRENT_SOURCE_DIR видно лише тоді, коли проєкт ділиться на підпапки: перша завжди вказує на корінь, друга змінюється залежно від того, у якому CMakeLists.txt ви зараз.

Змінна CMAKE_CXX_COMPILER_ID для Clang від Apple має значення AppleClang, а не Clang. Умова CMAKE_CXX_COMPILER_ID STREQUAL "Clang" на macOS не спрацює. Для обох варіантів використовуйте MATCHES "Clang".

Власні змінні

# Прості змінні
set(MY_VAR "value")
set(MY_NUMBER 42)

# Списки: три еквівалентні записи
set(MY_LIST item1 item2 item3)
set(MY_LIST "item1;item2;item3")

# Додавання до списку
list(APPEND MY_LIST item4)

# Використання
message(STATUS "My variable: ${MY_VAR}")

Список у CMake — це звичайний рядок, де елементи розділені крапкою з комою. Саме тому set(MY_LIST item1 item2 item3) і set(MY_LIST "item1;item2;item3") дають однаковий результат. Реальний вивід підтверджує це:

set(MY_LIST item1 item2 item3)
list(APPEND MY_LIST item4)
message("список: ${MY_LIST}")
list(LENGTH MY_LIST N)
message("довжина: ${N}")
cmake -P list.cmake
$ cmake -P list.cmake
список: item1;item2;item3;item4
довжина: 4
Коли список передається у foreach або add_executable без лапок (${SOURCES}), CMake розбиває його на окремі елементи за крапкою з комою. Якщо ж взяти змінну в лапки ("${SOURCES}"), вона перетворюється на один аргумент із крапками з комою всередині. Для файлів потрібен запис без лапок.

Область видимості змінних

Змінна, встановлена в підпапці (через add_subdirectory) або у функції, діє лише там. Щоб передати значення вгору, використовують PARENT_SCOPE. Докладніше про це в лекції про функції та макроси.

Коментарі

# Це однорядковий коментар

#[[
Це
багаторядковий
коментар
]]

# Коментарі можна писати українською, англійською або будь-якою іншою мовою

Багаторядковий коментар #[[ ... ]] зручний, щоб тимчасово вимкнути блок команд під час налагодження.

Повний приклад базового CMakeLists.txt

Нижче зібрано все розглянуте в одному файлі: версія, опис проєкту, стандарт, попередження компілятора, діагностика.

# Мінімальна версія CMake
cmake_minimum_required(VERSION 3.10)

# Опис проєкту
project(CalculatorApp
    VERSION 1.0.0
    DESCRIPTION "Simple calculator application"
    LANGUAGES CXX
)

# Налаштування C++
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)  # Стандартний C++ без розширень компілятора

# Опції компіляції
if(MSVC)
    # Visual Studio
    add_compile_options(/W4)  # Максимум попереджень
else()
    # GCC/Clang
    add_compile_options(-Wall -Wextra -pedantic)
endif()

# Виведення інформації
message(STATUS "=== Configuring ${PROJECT_NAME} ===")
message(STATUS "Version: ${PROJECT_VERSION}")
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

# Список файлів
set(SOURCES
    main.cpp
    calculator.cpp
    calculator.h
)

# Створення виконуваного файлу
add_executable(${PROJECT_NAME} ${SOURCES})

# Повідомлення про успіх
message(STATUS "=== Configuration complete ===")

Розбір окремих місць

  • CMAKE_CXX_EXTENSIONS OFF. За замовчуванням CMake додає прапорець -std=gnu++17, який вмикає розширення GCC. Вимкнення дає -std=c++17, тобто чистий стандарт. Код, який випадково використовує розширення, на іншому компіляторі не збереться, тому краще дізнатися про це одразу.
  • add_compile_options(...). Додає прапорці до всіх цілей, створених після виклику. Попередження -Wall -Wextra -pedantic (для MSVC /W4) допомагають виловлювати помилки ще до запуску програми.
  • if(MSVC) ... else() ... endif(). Прапорці MSVC та GCC/Clang несумісні, тому їх обирає умова. Умовні конструкції розглянемо докладно в окремій лекції.
  • ${PROJECT_NAME} в add_executable. Ім'я програми береться з назви проєкту, тому при перейменуванні проєкту достатньо змінити один рядок.

Фактичний вивід конфігурації цього файлу (з порожнім main.cpp):

cmake -S . -B build
$ cmake -S . -B build
-- === Configuring CalculatorApp ===
-- Version: 1.0.0
-- System: Darwin
-- Compiler: AppleClang 21.0.0.21000334
-- Build type:
-- === Configuration complete ===
-- Build files have been written to: /Users/student/CalculatorApp/build

Рядок Build type: порожній, бо тип збірки не задано. Це нормальна поведінка для single-config генераторів: порожній тип означає збірку без додаткових прапорців оптимізації. Налаштуванню типів збірки присвячена окрема лекція.

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

Резюме

📜 Команди

cmake_minimum_required, project, set, add_executable, add_library, target_link_libraries, message покривають основу будь-якого CMakeLists.txt.

🧮 Змінні

Вбудовані змінні (PROJECT_*, CMAKE_*) описують проєкт, систему та компілятор. Власні задаються через set і читаються як ${ІМ'Я}.

📋 Списки

Список — рядок із крапкою з комою. Без лапок розгортається в окремі аргументи, у лапках лишається одним.

🔎 Діагностика

message(STATUS ...) показує значення змінних під час конфігурації і допомагає зрозуміти, що саме бачить CMake.
Далі розглянемо проєкти з багатьма файлами: три способи перелічити джерела, чому file(GLOB) вважається ризикованим і як працює CONFIGURE_DEPENDS.
Copyright © 2026