- PVSM.RU - https://www.pvsm.ru -
CMake — это система сборки для C/C++, которая с каждым годом становится всё популярнее. Он практически стал решением по умолчанию для новых проектов. Однако, множество примеров выполнения какой-либо задачи на CMake содержат архаичные, ненадёжные, раздутые действия. Мы выясним, как писать скрипты сборки на CMake лаконичнее.
Если вы хотите опробовать советы в деле, возьмите пример на github и исследуйте его по мере чтения статьи: https://github.com/sergey-shambir/modern-cmake-sample [1]
Совет не относится к тем, кто пишет публичные библиотеки, поскольку для них важна совместимость со старым окружением разработки. А если вы пишете проект с закрытым кодом либо узкоспециальное опенсорсное ПО, то можно потребовать от всех разработчиков поставить последнюю версию CMake. Без этого многие советы статьи работать не будут! На момент написания статьи мы имеем CMake 3.8.
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
Современный CMake умеет сам вызывать систему сборки. В документации CMake [2] такой режим называется Build Tool Mode.
# Переходим из каталога myproj в myproj-build
mkdir ../myproj-build && cd ../myproj-build
# Конфигурируем для сборки из исходников в ../myproj
cmake -DCMAKE_BUILD_TYPE=Release ../myproj
# Запускаем сборку в текущем каталоге
cmake --build .
# Запускаем сборку, передаём ключ '-j4' низлежащей системе сборки.
cmake --build . -- -j4
Если вы генерируете проект Visual Studio, вы также можете собрать его из командной строки, в том числе можно собрать конкретный проект в конкретной конфигурации:
cmake --build .
--target myapp
--config Release
--clean-first
На Linux не используйте make install, иначе вы засорите свою систему. Об этом есть отдельная статья Хочется взять и расстрелять, или ликбез о том, почему не стоит использовать make install [3]
Вложенность CMakeLists.txt — это нормально. Если ваш проект разделён на 3 библиотеки, 3 набора тестов и 2 приложения, то почему бы не добавить CMakeLists.txt
для каждого из них? Тогда вам потребуется создать ещё один центральный CMakeLists.txt
, и в нём выполнить add_subdirectory [4]. Так может выглядеть центральный CMakeLists:
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(opengl-samples)
# Лайфхак: объявленные в старшем CMakeLists функции
# будут видны в подпроектах, включённых через add_subdirectory
include(scripts/functions.cmake)
add_subdirectory(libs/libmath)
add_subdirectory(libs/libplatform)
add_subdirectory(libs/libshade)
# Инструкция enable_testing неявно объявляет опцию BUILD_TESTING,
# по умолчанию BUILD_TESTING=ON.
# Вызывайте `cmake -DBUILD_TESTING=OFF projectdir` из командной строки,
# если не хотите собирать тесты.
enable_testing()
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
# ..остальные цели..
Не заводите глобальных переменных без крайней необходимости. Не используйте link_directories()
, include_directories()
, add_definitions()
, add_compile_options()
и другие подобные инструкции.
# Добавляем цель-библиотеку
add_library(mylibrary
ColorDialog.h ColorDialog.cpp
ColorPanel.h ColorPanel.cpp)
# ! Осторожно - непереносимый код !
# Добавляем к цели путь поиска заголовков /usr/include/wx-3.0
# Лучше использовать find_package для получения пути к заголовкам.
target_include_directories(mylibrary /usr/include/wx-3.0)
Стоит заметить, что
target_link_libraries
может добавить пути поиска заголовков библиотеки, если библиотека находится в вашем проекте и к ней были прикреплены пути поиска заголовков через конструкциюtarget_include_directories(libfoo PUBLIC ...)
.
Есть пример схемы зависимостей, взятый из презентации Modern CMake / an Introduction [9] за авторством Tobias Becker:
В последние годы стандарт C++ обновляется часто: мы получили потрясающие изменения в C++11, C++14, C++17. Старайтесь по возможности отказаться от старых компиляторов. Например, для Linux ничто не мешает установить последнюю версию Clang и libc++ и начать собирать все проекты со статической компоновкой C++ runtime.
Лучший способ включить C++17 без игры с флагами компиляции — явно сказать CMake, что он вам нужен.
# Способ первый: затребовать от компилятора фичу cxx_std_17
target_compile_features(${TARGET} PUBLIC cxx_std_17)
# Способ второй: указать компилятору на стандарт
set_target_properties(${TARGET} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
С помощью target_compile_features
вы можете требовать не C++17 или C++14, а определённых фич со стороны компилятора. Полный список известных CMake фич компиляторов можно посмотреть в документации [10].
В CMake можно объявлять свои функциональные макросы и свои функции. Есть лишь одно различие между ними: переменные, установленные внутри функции, являются локальными.
Удобно писать функции, чтобы решать текущие проблемы кастомизации сборки либо упрощать добавление множества целей сборки. Пример ниже был написан для более корректного включения C++17 из-за того, что
/std:c++latest
для включения C++17std::experimental::filesystem
в Clang/libc++ нужно указать компоновщику, что проект надо линковать с libc++experimental.a
, поскольку в libc++.a
модуля filesystem пока ещё нет; также нужно линковать с pthread, поскольку реализация thread/mutex и т.п. опирается на pthread# В текущей версии CMake не может включить режим C++17 в некоторых компиляторах.
# Функция использует обходной манёвр.
function(custom_enable_cxx17 TARGET)
# Включаем C++17 везде, где CMake может.
target_compile_features(${TARGET} PUBLIC cxx_std_17)
# Включаем режим C++latest в Visual Studio
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "/std:c++latest")
# Включаем компоновку с libc++, libc++experimental и pthread для Clang
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "-stdlib=libc++ -pthread")
target_link_libraries(${TARGET} c++experimental pthread)
endif()
endfunction(custom_enable_cxx17)
Каждая функция — это по сути хак, созданный для переопределения языка CMake или его поведения. Для других разработчиков смысл этого хака неясен. Поэтому старайтесь к каждой инструкции в функции добавлять комментарий, объясняющий её цель и смысл.
В крупных открытых проектах, например в KDE, применение своих функций может быть дурным тоном. Вы можете рассмотреть иные варианты: писать скрипт сборки явно по принципу "Explicit is better then implicit", либо даже предложить добавить свою функцию в upstream проекта CMake.
Мой коллега разрабатывает вне работы маленький 3D движок для рендеринга сцены с моделями и анимациями через OpenGL, GLES, DirectX и Vulkan. Однажды мы с ним обсуждали этот проект, и оказалось, что для сборки под все платформы (Windows, Linux, Android) он использует Visual Studio! Он недоволен тем, что Microsoft редко обновляет Android NDK, но не хочет отказываться от сборки через MSBuild по одной простой причине.
Ему не хочется сопровождать список файлов для сборки в двух системах сборки.
Когда-то я вёл портирование игры с iOS на Android, и мы поддерживали две системы сборки с помощью скрипта, который читал проект XCode и автоматически дополнял список файлов в Android.mk
. Если вы используете CMake, то вам даже скрипт не нужно писать.
В CMake есть функция aux_source_directory
, но она имеет недостаток: заголовки не добавляются в список и не появляются в любом сгенерированном проекте для IDE.
file(GLOB ...)
, сканирующий файлы по маскеcustom_add_executable_from_dir(name)
CMAKE_CURRENT_SOURCE_DIR
function(custom_add_executable_from_dir TARGET)
# Собираем файлы с текущего каталога
file(GLOB TARGET_SRC "CMAKE_CURRENT_SOURCE_DIR/*.cpp"
# Добавляем исполняемый файл
add_executable(${TARGET} ${TARGET_SRC})
endfunction()
Вы можете добавить функцию custom_add_library_from_dir
для целей-библиотек аналогичным путём.
Если же вы — фанат ручной работы или создаёте публичную библиотеку, тогда, возможно, вам лучше добавлять файлы по одному. В этом случае используйте target_sources
для добавления платформо-специфичных файлов:
add_library(libfoo Foo.h Foo_common.cpp)
if(WIN32)
target_sources(libfoo Foo_win32.cpp)
endif(WIN32)
Наверняка вам хотелось ради автоматизации вызвать из cmake команду Bash, чтобы создать каталог, распаковать архив или подсчитать md5 сумму. Но вызов утилит командной строки может лишить проект кроссплатформенности. Более переносимый метод — вызывать cmake -E команда
, пользуясь Command-Line Tool Mode [11].
Совет относится к вам, если вы пишете публично доступные библиотеки. В этом случае вам стоит упрощать следующие сценарии:
CMakeLists.txt
через add_subdirectory
Добавляя библиотеку, создавайте ещё и уникальный синоним:
# Добавляем цель-библиотеку
add_library(foo ${FOO_SRC})
# Добавляем синоним, содержащий имя выпускающей библиотеку организации
add_library(MyOrg::foo ALIAS foo)
Оставляйте пользователям библиотеки право использования опции BUILD_SHARED_LIBS
для выбора между сборкой статической и динамической версий библиотеки.
При установке настроек компоновки, поиска заголовков и флагов компиляции для библиотек используйте ключевые слова PUBLIC, PRIVATE, INTERFACE, чтобы позволить целям, зависящим от вашей библиотеки, наследовать необходимые настройки:
target_link_libraries(foobarapp
PUBLIC MyOrg::libfoo
PRIVATE MyOrg::libbar
)
Подсистема CTest не заставляет вас использовать какие-то особые библиотеки для тестирования вместо привычных Boost.Test, Catch или Google Tests. Она всего лишь регистрирует автотесты так, чтобы CMake мог запустить все тесты или выбранные тесты одной командой ctest
.
Чтобы включить поддержку CTest по всему проекту, есть инструкция enable_testing
# Инструкция enable_testing неявно объявляет опцию BUILD_TESTING,
# по умолчанию BUILD_TESTING=ON.
# Вызывайте `cmake -DBUILD_TESTING=OFF projectdir` из командной строки,
# если не хотите собирать тесты.
enable_testing()
if(BUILD_TESTING)
add_subdirectory(tests/libhellotest)
add_subdirectory(tests/libgoodbyetest)
endif()
Чтобы исполняемый файл с тестом был зарегистирован в CTest, нужно вызвать инструкцию add_test
.
# Исполняемый файл теста - это обычная исполняемая цель сборки
add_executable(${TARGET} ${TARGET_SRC})
# Регистрируем исполняемый файл в CMake как набор тестов.
# можно назначить тесту особое имя, но проще использовать имя исполняемого файла теста.
add_test(${TARGET} ${TARGET})
Перед созданием статьи были прочитаны, опробованы и переосмыслены несколько англоязычных источников:
Некоторые советы из этих источников никак не отражены в статье. Поэтому после их прочтения вы определённо станете глубже разбираться в CMake.
Автор: sergey_shambir
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/257863
Ссылки в тексте:
[1] https://github.com/sergey-shambir/modern-cmake-sample: https://github.com/sergey-shambir/modern-cmake-sample
[2] В документации CMake: https://cmake.org/cmake/help/latest/manual/cmake.1.html#build-tool-mode
[3] Хочется взять и расстрелять, или ликбез о том, почему не стоит использовать make install: https://habrahabr.ru/post/130868/
[4] add_subdirectory: https://cmake.org/cmake/help/latest/command/add_subdirectory.html
[5] target_link_libraries: https://cmake.org/cmake/help/latest/command/target_link_libraries.html
[6] target_include_directories: https://cmake.org/cmake/help/latest/command/target_include_directories.html
[7] target_compile_definitions: https://cmake.org/cmake/help/latest/command/target_compile_definitions.html
[8] target_compile_options: https://cmake.org/cmake/help/latest/command/target_compile_options.html
[9] Modern CMake / an Introduction: https://docs.google.com/presentation/d/18fY0zDtJCMUW5WdY2ZOfKtvb7lXEbBPFe_I6MNJC0Qw
[10] посмотреть в документации: https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html
[11] Command-Line Tool Mode: https://cmake.org/cmake/help/latest/manual/cmake.1.html#command-line-tool-mode
[12] Modern CMake / an Introduction: https://docs.google.com/presentation/d/18fY0zDtJCMUW5WdY2ZOfKtvb7lXEbBPFe_I6MNJC0Qw/edit#slide=id.g15c9891529_0_2
[13] CMake — Introduction and best practices: https://www.slideshare.net/DanielPfeifer1/cmake-48475415
[14] Modern CMake with target_link_libraries: https://schneide.wordpress.com/2016/04/08/modern-cmake-with-target_link_libraries/
[15] Enabling C++11 And Later In CMake: https://crascit.com/2015/03/28/enabling-cxx11-in-cmake/
[16] Источник: https://habrahabr.ru/post/330902/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.