Синтаксис CMakeLists.txt: команди, змінні, списки
Синтаксис 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().
target_link_libraries: зв'язування
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}")
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):
Рядок Build type: порожній, бо тип збірки не задано. Це нормальна поведінка для single-config генераторів: порожній тип означає збірку без додаткових прапорців оптимізації. Налаштуванню типів збірки присвячена окрема лекція.
Питання для самоперевірки
b і c. Крапка з комою в CMake і є роздільником елементів списку.main.cpp;utils.cpp. CMake спробує знайти файл із такою назвою і видасть помилку. Для переліку файлів використовуйте ${SOURCES} без лапок.add_compile_options впливає лише на цілі, створені після виклику. Якщо поставити її після add_executable, поточна ціль залишиться без прапорців. У сучасному стилі прапорці краще вказувати конкретній цілі через target_compile_options, що розглянемо далі.CMAKE_SOURCE_DIR — корінь всього дерева збірки, тобто папка найвищого CMakeLists.txt. PROJECT_SOURCE_DIR — папка, де востаннє викликали project(). Вони збігаються у простих проєктах. Якщо ваш проєкт підключають як підпроєкт до іншого, значення різні, і саме тому для власних шляхів корисніше PROJECT_SOURCE_DIR.Резюме
📜 Команди
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.Перший проєкт з CMake: конфігурація, збірка, запуск
Мінімальний CMakeLists.txt, збірка за схемою out-of-source, вміст папки build, команди cmake -S -B та cmake --build, вибір генератора.
Проєкт із кількома файлами: перелік джерел і file(GLOB)
Три способи вказати файли проєкту: явний перелік, змінні SOURCES і HEADERS, автоматичний пошук через file(GLOB). Чому GLOB ризикований і як працює CONFIGURE_DEPENDS.