Все статьи Шпаргалка по CMake
См. также статью Современный CMake: 10 советов по улучшению скриптов сборки.
Терминология
- Файл
CMakeLists.txt
служит скриптом (рецептом, сценарием) сборки проекта. Обычно один такой файл собирает все исходники в своём каталоге и в подкаталогах, при этом подкаталоги могут содержать, а могут не содержать дочерние файлыCMakeLists.txt
. С точки зрения IDE, таких как CLion или Visual Studio, файлCMakeLists.txt
также служит проектом, с которым работает программист внутри IDE. - В cmake есть “цель” (“target”) - компонент, который следует собрать. Компонент может быть исполняемым файлом, так и статической либо динамической библиотекой.
- В cmake есть “проект” (“project”) - это набор компонентов, по смыслу похожий на Solution в Visual Studio.
- В cmake есть “флаги” (flags) - это аргументы командной строки для компилятора, компоновщика и других утилит, вызываемых при сборке.
- В cmake есть переменные, и в процессе интерпретации файла
CMakeLists.txt
система сборки cmake вычисляет ряд встроенных переменных для каждой цели, тем самым получая флаги. Затем cmake создаёт вторичный скрипт сборки, который будет напрямую вызывать компилятор, компоновщик и другие утилиты с вычисленными флагами.
Сборка проекта из командной строки (Linux)
На первом шаге проект нужно сконфигурировать, то есть создать финальный скрипт сборки, запустив cmake <параметры> <путь-к-каталогу>
в будущем каталоге сборки.
# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Создадим каталог `myapp-release` и перейдём в него.
mkdir --parents ../myapp-release
cd ../myapp-release
# Сконфигурируем проект для сборки в Release.
# Флаг установит опцию CMAKE_BUILD_TYPE в значение "Release",
# интерпретатор cmake считает это переключением на Release конфигурацию.
cmake -DCMAKE_BUILD_TYPE=Release ../myapp
На втором шаге нужно запустить финальный скрипт. Не вызывайте make
! Утилита cmake сделает это сама:
# Просим CMake запустить сборку в каталоге `myapp-release`
# Можно добавить флаг `--target mylibrary` для сборки только mylibrary
# Можно добавить флаг `--clean-first`, чтобы в начале новой сборки
# стирать остатки предыдущей.
cmake --build .
# Аналогичный способ для GNU/Linux. Его по привычке советуют в сети, хотя
# make доступен не всегда, а cmake --build работает на всех платформах.
make
Структура CMakeLists.txt
В начале главного файла CMakeLists.txt
ставят метаинформацию о минимальной версии CMake и названии проекта:
# Указывайте последнюю доступную вам версию CMake.
cmake_minimum_required(VERSION 3.8)
# Синтаксис: project(<имя> VERSION <версия> LANGUAGES CXX),
# теги VERSION и LANGUAGES необязательные.
project(libmodel3d)
Затем следует список инструкций, служащих для вычисления различных переменных, создания целей сборки,
подключения проектов из подкаталогов и так далее. Например, подключить дополнительный CMakeLists.txt
из подкаталога можно так:
# Простая версия: подключает скрипт по пути <подкаталог>/CMakeLists.txt
add_subdirectory(<подкаталог>)
# Расширенная версия: дополнительно установит подкаталог сборки подпроекта
add_subdirectory(<подкаталог> <подкаталог сборки>)
Целью может стать исполняемый файл, собираемый из исходного кода
# Синтаксис: add_executable(<имя> <список исходников...>)
# Добавлять `.h` необязательно, но лучше для работы из IDE:
# - IDE определит заголовок как часть проекта
# - cmake будет отслеживать изменения в заголовке и пересобирать
# проект при изменениях.
add_executable(pngconverter main.cpp PngReader.h PngReader.cpp)
Целью также может быть библиотека, статическая или динамическая.
# Синтаксис: add_library(<имя> [STATIC|SHARED|INTERFACE] <список исходников...>)
# Тип библиотеки (staic или shared) зависит от параметров сборки
add_library(libpngutils PngUtils.h PngUtils.cpp)
# Тип библиотеки: static
add_library(libpngtools STATIC PngTools.h PngTools.cpp)
Автогенерация проекта для Visual Studio (Windows)
Если используется Visual C++, то путь немного другой: на шаге конфигурирования создаётся проект для Visual Studio, который затем можно собрать из IDE либо так же из командной строки.
Созданный проект Visual Studio нельзя изменять и использовать постоянно, потому что при генерации проекта используются абсолютные пути и другие неприемлемые для постоянной работы вещи.
# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Сгенерируем проект Visual Studio для сборки.
mkdir --parents ../myapp-build
cd ../myapp-build
# Конфигурируем для сборки с Visual Studio 2017, версия тулчейна v140
cmake -G "Visual Studio 2017"
Если проект был сконфигурирован успешно, то в каталоге ../myapp-build
появятся автоматически сгенерированный BUILD_ALL.sln и проекты для Visual Studio. Их можно открыть к IDE, либо собрать из командной строки с помощью cmake. Названия опций говорят сами за себя:
cmake --build . \
--target myapp \
--config Release \
--clean-first
Зависимости между библиотеками и приложениями
Не используйте директивы include_directories
, add_definitions
, add_compile_options
!
Они меняют глобальные настройки для всех целей, это создаёт проблемы при масштабировании.
- Используйте target_link_libraries для добавления статических и динамических библиотек, от которых зависит цель
- Используйте target_include_directories вместо include_directories для добавления путей поиска заголовков, от которых зависит цель
- Используйте target_compile_definitions вместо add_definitions для добавления макросов, с которыми собирается цель
- Используйте target_compile_options для добавления специфичных флагов компилятора, с которыми собирается цель
Пример:
# Добавляем цель - статическую библиотеку
add_library(mylibrary STATIC \
ColorDialog.h ColorDialog.cpp \
ColorPanel.h ColorPanel.cpp)
# ! Осторожно - непереносимый код !
# Добавляем к цели путь поиска заголовков /usr/include/wx-3.0
# Лучше использовать find_package для получения пути к заголовкам.
target_include_directories(mylibrary /usr/include/wx-3.0)
Вы можете выбирать область видимости настройки:
PUBLIC
делает настройку видимой для текущей цели и для всех зависящих от неё целейPRIVATE
делает настройку видимой только для текущей целиINTERFACE
делает настройку видимой только для всех зависящих от неё целей
Пример использования областей видимости:
# Каталог include будет добавлен к путям поиска заголовков в текущей цели и во всех зависимых целях
target_include_directories(myTarget PUBLIC ./include)
# Каталог src будет добавлен к путям поиска заголовков только в текущей цели
target_include_directories(myTarget PUBLIC ./src)
Схема зависимостей условного проекта:
Выбор стандарта и диалекта C++
Для настройки стандарта и флагов языка C++ не добавляйте флаги напрямую!
# ! Устаревший метод - прямое указание флага !
target_compile_options(hello PRIVATE -std=c++11)
В CMake версии 3.8+ вы можете прямо потребовать включить нужный стандарт:
# Источник: https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html
# Включаем C++ 2017
target_compile_features(myapp cxx_std_17)
# Альтернатива: включаем C++ 2014
target_compile_features(myapp cxx_std_14)
# Альтернатива: включаем C++ 2011
target_compile_features(myapp cxx_std_11)
В CMake версии до 3.7 включительно можно использовать set_target_properties
(если не работает, то у вас слишком старый CMake):
# Стандарт: C++ 2014, расширения языка от производителя компилятора отключены
set_target_properties(myapp PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
Для разработчиков библиотек есть более тонкий контроль над возможностями языка:
# API библиотеки (т.е. заголовки) требуют лямбда-функций и override,
# реализация библиотеки требует ещё и range-based for.
target_compile_features(mylibrary PUBLIC cxx_override cxx_lambdas PRIVATE cxx_range_for)
Функции в CMake
CMake позволяет объявлять функции командами function(name) / endfunction()
и макросы командами macro(name) / endmacro()
. Предпочитайте функции, а не макросы, т.к. у функций есть своя область видимости переменных, а у макросов - нет.
function(hello_get_something var_name)
...
# Установить переменную в области видимости вызывающей стороны
# можно с помощью тега PARENT_SCOPE
set(${var_name} ${ret} PARENT_SCOPE)
endfunction()
Добавление исходников к цели с target_sources
Лучше добавлять специфичные исходники с помощью target_sources, а не с помощью дополнительных переменных.
add_library(hello hello.cxx)
if(WIN32)
target_sources(hello PRIVATE system_win.cxx)
elseif(UNIX)
target_sources(hello PRIVATE system_posix.cxx)
else()
target_sources(hello PRIVATE system_generic.cxx)
endif()
Интерфейс к утилитам командной строки
Подробнее см. Command-Line Tool Mode
# Создать каталог debug-build
cmake -E make_directory debug-build
# Перейти в каталог debug-build
cmake -E chdir debug-build
Функция find_package
Функция find_package принимает имя библиотеки как аргумент и обращается к CMake, чтобы найти скрипт для настройки переменных данной библиотеки. В итоге при сборке либо возникает ошибка из-за того что пакет не найден, либо добавляются переменные, хранящие пути поиска заголовков, имена библиотек для компоновщика и другие параметры.
Пример подключения Boost, вызывающего встроенный в CMake скрипт FindBoost:
# Весь Boost без указания конкретных компонентов
find_package(Boost REQUIRED)
# Теперь доступны переменные
# - Boost_INCLUDE_DIRS: пути к заголовочным файлам
# - Boost_LIBRARY_DIRS: пути к статическим/динамическим библиотекам
# - Boost_LIBRARIES: список библиотек для компоновщика
# - Boost_<C>_LIBRARY: библиотека для компоновки с компонентом <C> библиотек Boost
Пример подключения библиотеки Bullet с помощью встроенного скрипта FindBullet и компоновки с приложением my_app:
# Вызываем встроенный скрипт FindBullet.cmake
find_package(Bullet REQUIRED)
# Добавляем пути поиска заголовков к цели my_app
target_include_directories(my_app ${BULLET_INCLUDE_DIRS})
# Добавляем список библиотек для компоновки с целью my_app
target_link_libraries(my_app ${BULLET_LIBRARIES})