Основы сборки проектов на С/C++ при помощи CMake
10 мая 2017
Некоторое время назад мы познакомились с Autotools. Несмотря на то, что Autotools до сих пор используется во многих известных проектах с открытым исходным кодом, инструмент этот трудно назвать особо удобным. Кроме того, нормально работает он только в *nix системах, а в каком-нибудь Windows пользоваться Autotools, скажем так, весьма непросто. В общем, Autotools — это легаси, и нормальные программисты в наше время пытаются использовать CMake или, например, SCons. В этой заметке мы познакомимся с CMake.
Говоря простыми словами, CMake — это такая штука, в которой вы описываете проект, а она вам генерирует Makefile’ы в *nix системах, проекты Visual Studio под Windows, файлы конкретных редакторов и IDE, например Sublime Text, Code::Blocks, Eclipse или KDevelop, и так далее. Несмотря на спорный в некоторых моментах синтаксис, в последнее время CMake становится стандартом де-факто в мире C/C++. В частности, CMake используется в LLVM, Qt, MariaDB, Blender, KiCad, GNU Radio и ряде других проектов. Кроме того, в CLion, IDE для C/C++ от компании JetBrains, по умолчанию также создаются проекты, основанные на CMake.
Примечание: В этом контексте вас может заинтересовать заметка Как править в CLion код любых проектов на С++, даже тех, в которых не используется CMake.
Использование CMake в простейшем случае выглядит следующим образом. В корне репозитория создается файл CMakeLists.txt примерно такого содержания:
# так пишутся комментарии
project(project_name)
find_library(PTHREAD_LIBRARY pthread)
find_library(PCRE_LIBRARY pcre)
include_directories(include)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
add_executable(main src/Main.cpp src/HttpServer.cpp)
target_link_libraries(main ${PTHREAD_LIBRARY} ${PCRE_LIBRARY})
Хочется надеяться, какая строчка здесь что означает, пояснять не нужно. Затем исходники складываются в каталог src, а заголовочные файлы — в каталог include. Для сборки проекта говорим:
cd build
cmake ..
make
Просто, не правда ли?
Помимо приведенного выше find_library
в CMake есть ряд скриптов для подключения конкретных библиотек. В частности, подключение OpenGL осуществляется как-то так:
include_directories(${OPENGL_INCLUDE_DIR})
target_link_libraries(main ${OPENGL_LIBRARY} ${CMAKE_DL_LIBS})
CMake можно указать конкретный тип Makefile’ов, которые вы хотите получить на выходе:
cmake -G "MinGW Makefiles" ..
# для просмотра списка всех доступных генераторов:
cmake -G
В частности, многие программисты для ускорения сборки проектов предпочитают использовать Ninja:
ninja -j1
Выбор между отладочной и релизной сборкой осуществляется так:
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja ..
cmake -DCMAKE_BUILD_TYPE=MinSizeRel -G Ninja ..
# Debug используется по умолчанию
cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja ..
Вместо запуска напрямую make или ninja можно сказать что-то вроде:
Можно выбрать конкретный компилятор для сборки проекта
-DCMAKE_CXX_COMPILER=`which clang++` -G Ninja ..
… а также указать дополнительные флаги компиляции:
Например, для определения степени покрытия кода тестами при помощи lcov нужно сказать что-то вроде:
-DCMAKE_EXE_LINKER_FLAGS="-lgcov" ..
В мире C/C++ нередко бывает, что сторонние библиотеки, использующие CMake, подключаются к проекту при помощи сабмодулей Git. Подключение таких библиотек к проекту осуществляется довольно просто:
project(c-algorithms-examples)
include_directories(deps/algorithms/include)
add_subdirectory(deps/algorithms/src)
add_executable(rbtree_example rbtree_example.c)
target_link_libraries(rbtree_example CAlgorithms)
В свою очередь, у библиотеки файл src/CMakeList.txt должен быть примерно таким:
project(c-algorithms)
add_library(CAlgorithms STATIC
struct/ilist.c
struct/rbtree.c
struct/htable.c
common/utils.c
)
Вообще, add_subdirectory может принимать путь до любого каталога, в котором есть файл CMakeLists.txt. Это позволяет разбивать проект на подпроекты даже в рамках одного репозитория. Опять же, в случае с библиотеками это позволяет поместить тесты в отдельный подпроект, который не будет собираться при подключении библиотеки в сторонние проекты.
Например, в корне библиотеки CMakeList.txt может быть таким:
project(c-algorithms-root)
enable_testing()
include_directories(include)
add_subdirectory(src)
add_subdirectory(test)
Непосредственно тесты добавляются в проект следующим образом:
project(c-algorithms-struct-tests)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g")
add_executable(test_htable test_htable.c)
target_link_libraries(test_htable CAlgorithms)
add_executable(test_rbtree test_rbtree.c)
target_link_libraries(test_rbtree CAlgorithms)
add_test(test_htable "./test_htable")
add_test(test_rbtree "./test_rbtree")
Запуск тестов осуществляется простой командой:
# или, с включением отладочного вывода:
make test ARGS="-V"
# или, если используете Ninja:
ninja test
… выполненной в каталоге build. Если вас интересует тема написания модульных тестов на C++, она более подробно раскрыта в заметке Тестирование кода на C++ с помощью Google Test.
Если же вы используете какой-нибудь PyTest, просто допишите в CMakeList.txt что-то вроде:
enable_testing()
add_test(NAME python_test
COMMAND py.test --capture=no ${CMAKE_SOURCE_DIR}/tests/run.py)
Вывод тестов пишется в файл Testing/Temporary/LastTest.log. Кстати, подробности о переменных окружения, доступных в CMake, таких, как CMAKE_SOURCE_DIR, можно найти здесь.
Помимо рассмотренных выше возможностей часто можно встретить поддержку сборки проектов с различными опциями. В частности, это используется в Assimp и LLDB. При сборке проекта опции выбираются так:
cmake -DASSIMP_BUILD_ASSIMP_TOOLS=OFF ...
Опции обычно описывают в документации, но в крайнем случае их можно посмотреть и через curses-интерфейс:
В рамках одного поста, конечно, не представляется возможным рассмотреть все возможности CMake. Однако представленной выше информации вам должно вполне хватить в 90% случаев. Полноценные рабочие примеры использования CMake вы найдете, например, в этом, этом, а также в этом репозиториях на GitHub. Примеры использования опций и условных операторов можно найти в репозиториях уже упомянутых Assimp и LLDB. Ну и, конечно же, массу полезного вы найдете на официальном сайте CMake.
А пользуетесь ли вы CMake и если да, используете ли какие-то его возможности, о которых не было рассказано выше?
Метки: C/C++, Кроссплатформенность.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.