Deartháireacha go deo iad CMake agus C++

Deartháireacha go deo iad CMake agus C++

В процессе разработки я люблю менять компиляторы, режимы сборки, версии зависимостей, производить статический анализ, замерять производительность, собирать покрытие, генерировать документацию и т.д. И очень люблю CMake, потому что он позволяет мне делать всё то, что я хочу.

Многие ругают CMake, и часто заслуженно, но если разобраться, то не всё так плохо, а в последнее время ní dona ar chor ar bith, agus tá treo na forbartha dearfach go leor.

В данной заметке я хочу рассказать, как достаточно просто организовать заголовочную библиотеку на языке C++ в системе CMake, чтобы получить следующую функциональность:

  1. Tionól;
  2. tástálacha Autorun;
  3. Tomhas clúdach cód;
  4. Suiteáil;
  5. Doiciméadúchán uathoibríoch;
  6. Giniúint bosca gainimh ar líne;
  7. Anailís statach.

Кто и так разбирается в плюсах и си-мейке может просто Íosluchtaigh múnla tionscadal teachín agus tús a úsáid.


Ábhar

  1. Tionscadal ón taobh istigh
    1. Struchtúr an tionscadail
    2. Príomhchomhad CMake (./CMakeLists.txt)
      1. Faisnéis tionscadail
      2. Roghanna Tionscadail
      3. Roghanna tiomsú
      4. An sprioc is mó
      5. Suiteáil
      6. Tástálacha
      7. Doiciméadú
      8. Bosca gainimh ar líne
    3. Script tástála (test/CMakeLists.txt)
      1. Tástáil
      2. Clúdach
    4. Script le haghaidh doiciméadú (doc/CMakeLists.txt)
    5. Script le haghaidh bosca gainimh ar líne (ar líne/CMakeLists.txt)
  2. Tionscadal lasmuigh
    1. Tionól
      1. Giniúint
      2. Tionól
    2. Roghanna
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Spriocanna an Tionóil
      1. De réir réamhshocraithe
      2. mylib-aonad-tástálacha
      3. seiceáil
      4. clúdach
      5. duga
      6. bosca slaite
    4. Примеры
  3. Uirlisí
  4. Anailís statach
  5. Afterword

Tionscadal ón taobh istigh

Struchtúr an tionscadail

.
├── CMakeLists.txt
├── README.en.md
├── README.md
├── doc
│   ├── CMakeLists.txt
│   └── Doxyfile.in
├── include
│   └── mylib
│       └── myfeature.hpp
├── online
│   ├── CMakeLists.txt
│   ├── mylib-example.cpp
│   └── wandbox.py
└── test
    ├── CMakeLists.txt
    ├── mylib
    │   └── myfeature.cpp
    └── test_main.cpp

Главным образом речь пойдёт о том, как организовать CMake-скрипты, поэтому они будут разобраны подробно. Остальные файлы каждый желающий может посмотреть непосредственно ar an leathanach teimpléad tionscadail.

Príomhchomhad CMake (./CMakeLists.txt)

Faisnéis tionscadail

В первую очередь нужно затребовать нужную версию системы CMake. CMake развивается, меняются сигнатуры команд, поведение в разных условиях. Чтобы CMake сразу понимал, чего мы от него хотим, нужно сразу зафиксировать наши к нему требования.

cmake_minimum_required(VERSION 3.13)

Затем обозначим наш проект, его название, версию, используемые языки и прочее (см. команду project).

Sa chás seo cuirimid in iúl an teanga CXX (а это значит C++), чтобы CMake не напрягался и не искал компилятор языка C (по умолчанию в CMake включены два языка: C и C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Здесь же можно сразу проверить, включён ли наш проект в другой проект в качестве подпроекта. Это сильно поможет в дальнейшем.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Roghanna Tionscadail

Cuirfimid dhá rogha ar fáil.

Is é an chéad rogha MYLIB_TESTING — tástálacha aonaid a dhíchumasú. D'fhéadfadh sé seo a bheith riachtanach má táimid cinnte go bhfuil gach rud in ord leis na tástálacha, ach ba mhaith linn ach, mar shampla, ár dtionscadal a shuiteáil nó a phacáistiú. Nó tá ár dtionscadal san áireamh mar fhothionscadal - sa chás seo, níl suim ag úsáideoir ár dtionscadal ár dtástálacha a reáchtáil. Ní dhéanann tú tástáil ar na spleáchais a úsáideann tú, an ndéanann tú?

option(MYLIB_TESTING "Включить модульное тестирование" ON)

Ina theannta sin, déanfaimid rogha ar leithligh MYLIB_COVERAGE для замеров покрытия кода тестами, но она потребует дополнительных инструментов, поэтому включать её нужно будет явно.

option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)

Roghanna tiomsú

Разумеется, мы крутые программисты-плюсовики, поэтому хотим от компилятора максимального уровня диагностик времени компиляции. Ни одна мышь не проскочит.

add_compile_options(
    -Werror

    -Wall
    -Wextra
    -Wpedantic

    -Wcast-align
    -Wcast-qual
    -Wconversion
    -Wctor-dtor-privacy
    -Wenum-compare
    -Wfloat-equal
    -Wnon-virtual-dtor
    -Wold-style-cast
    -Woverloaded-virtual
    -Wredundant-decls
    -Wsign-conversion
    -Wsign-promo
)

Расширения тоже отключим, чтобы полностью соответствовать стандарту языка C++. По умолчанию в CMake они включены.

if(NOT CMAKE_CXX_EXTENSIONS)
    set(CMAKE_CXX_EXTENSIONS OFF)
endif()

An sprioc is mó

Níl sa leabharlann seo ach comhaid cheanntásc, rud a chiallaíonn nach bhfuil aon sceite againn i bhfoirm leabharlanna statacha nó dinimiciúla. Ar an láimh eile, chun ár leabharlann a úsáid go seachtrach, ní mór í a shuiteáil, ní mór é a bheith inbhraite sa chóras agus ceangailte le do thionscadal, agus ag an am céanna na ceannteidil chéanna seo, chomh maith le roinnt cinn breise b'fhéidir, ag gabháil leis airíonna.

Chun na críche sin, cruthaímid leabharlann comhéadan.

add_library(mylib INTERFACE)

Ceanglaíonn muid ceanntásca lenár leabharlann comhéadain.

Современное, модное, молодёжное использование CMake подразумевает, что заголовки, свойства и т.п. передаются через одну единственную цель. Таким образом, достаточно сказать target_link_libraries(target PRIVATE dependency), agus gach ceanntásc a bhaineann leis an sprioc dependency, ar fáil d'fhoinsí a bhaineann leis an sprioc target. Agus ní gá duit aon [target_]include_directories. Léireofar é seo thíos san anailís Script CMake le haghaidh tástálacha aonaid.

Is fiú aird a thabhairt freisin ar an mar a thugtar air. выражения-генераторы: $<...>.

Данная команда ассоциирует нужные нам заголовки с нашей интерфейсной библиотекой, причём, в случае, если наша библиотека будет подключена к какой-либо цели в рамках одной иерархии CMake, то с ней будут ассоциированы заголовки из директории ${CMAKE_CURRENT_SOURCE_DIR}/include, а если наша библиотека установлена в систему и подключена в другой проект с помощью команды find_package, ansin beidh ceanntásca ón eolaire bainteach leis include i gcoibhneas leis an eolaire suiteála.

target_include_directories(mylib INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

Установим стандарт языка. Разумеется, самый последний. При этом не просто включаем стандарт, но и распространяем его на тех, кто будет использовать нашу библиотеку. Это достигается за счёт того, что установленное свойство имеет категорию INTERFACE (Féach ordú target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Заводим псевдоним для нашей библиотеки. Причём для красоты он будет в специальном «пространстве имён». Это будет полезно, когда в нашей библиотеке появятся разные модули, и мы заходим подключать их независимо друг от друга. Cosúil i Busta, mar shampla.

add_library(Mylib::mylib ALIAS mylib)

Suiteáil

Установка наших заголовков в систему. Тут всё просто. Говорим, что папка со всеми заголовками должна попасть в директорию include i gcoibhneas leis an suíomh suiteála.

install(DIRECTORY include/mylib DESTINATION include)

Далее сообщаем системе сборки о том, что мы хотим иметь возможность в сторонних проектах звать команду find_package(Mylib) agus sprioc a fháil Mylib::mylib.

install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)

Следующее заклинание нужно понимать так. Когда в стороннем проекте мы вызовем команду find_package(Mylib 1.2.3 REQUIRED), и при этом реальная версия установленной библиотеки окажется несовместимой с версией 1.2.3, CMake автоматически сгенерирует ошибку. То есть не нужно будет следить за версиями вручную.

include(CMakePackageConfigHelpers)
write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake"
    VERSION
        ${PROJECT_VERSION}
    COMPATIBILITY
        AnyNewerVersion
)
install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)

Tástálacha

Má tá tástálacha díchumasaithe go sainráite ag baint úsáide as rogha chomhfhreagrach или наш проект является подпроектом, то есть подключён в другой CMake-проект с помощью команды add_subdirectory, мы не переходим дальше по иерархии, и скрипт, в котором описаны команды для генерации и запуска тестов, просто не запускается.

if(NOT MYLIB_TESTING)
    message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
    add_subdirectory(test)
endif()

Doiciméadú

Ní ghinfear doiciméadú ach oiread i gcás fothionscadail.

if(NOT IS_SUBPROJECT)
    add_subdirectory(doc)
endif()

Bosca gainimh ar líne

Аналогично, онлайн-песочницы у подпроекта тоже не будет.

if(NOT IS_SUBPROJECT)
    add_subdirectory(online)
endif()

Script tástála (test/CMakeLists.txt)

Tástáil

Первым делом находим пакет с нужным тестовым фреймворком (замените на свой любимый).

find_package(doctest 2.3.3 REQUIRED)

Создаём наш исполняемый файл с тестами. Обычно непосредственно в исполняемый бинарник я добавляю только файл, в котором будет функция main.

add_executable(mylib-unit-tests test_main.cpp)

А файлы, в которых описаны сами тесты, добавляю позже. Но так делать не обязательно.

target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)

Подключаем зависимости. Обратите внимание, что к нашему бинарнику мы привязали только нужные нам CMake-цели, и не вызывали команду target_include_directories. Ceannteidil ón gcreat tástála agus ónár gceann Mylib::mylib, а также параметры сборки (в нашем случае это стандарт языка C++) пролезли вместе с этими целями.

target_link_libraries(mylib-unit-tests
    PRIVATE
        Mylib::mylib
        doctest::doctest
)

Наконец, создаём фиктивную цель, «сборка» которой эквивалентна запуску тестов, и добавляем эту цель в сборку по умолчанию (за это отвечает атрибут ALL). Это значит, что сборка по умолчанию инициирует запуск тестов, то есть мы никогда не забудем их запустить.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Clúdach

Далее включаем замер покрытия кода, если задана соответствующая опция. В детали вдаваться не буду, потому что они относятся больше к инструменту для замеров покрытия, чем к CMake. Важно только отметить, что по результатам будет создана цель coverage, a bhfuil sé áisiúil clúdach a thomhas a thosú.

find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "Измерение покрытия кода тестами включено")

    target_compile_options(mylib-unit-tests PRIVATE --coverage)
    target_link_libraries(mylib-unit-tests PRIVATE gcov)

    add_custom_target(coverage
        COMMAND
            ${GCOVR_EXECUTABLE}
                --root=${PROJECT_SOURCE_DIR}/include/
                --object-directory=${CMAKE_CURRENT_BINARY_DIR}
        DEPENDS
            check
    )
elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE)
    set(MYLIB_COVERAGE OFF)
    message(WARNING "Для замеров покрытия кода тестами требуется программа gcovr")
endif()

Script le haghaidh doiciméadú (doc/CMakeLists.txt)

Aimsíodh Doxygen.

find_package(Doxygen)

Дальше проверяем, установлена ли пользователем переменная с языком. Если да, то не трогаем, если нет, то берём русский. Затем конфигурируем файлы системы Doxygen. Все нужные переменные, в том числе и язык попадают туда в процессе конфигурации (см. команду configure_file).

Ansin cruthaímid sprioc doc, которая будет запускать генерирование документации. Поскольку генерирование документации — не самая большая необходимость в процессе разработки, то по умолчанию цель включена не будет, её придётся запускать явно.

if (Doxygen_FOUND)
    if (NOT MYLIB_DOXYGEN_LANGUAGE)
        set(MYLIB_DOXYGEN_LANGUAGE Russian)
    endif()
    message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}")
    configure_file(Doxyfile.in Doxyfile)
    add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()

Script le haghaidh bosca gainimh ar líne (ar líne/CMakeLists.txt)

Anseo aimsímid an tríú Python agus cruthaítear sprioc wandbox, a ghineann iarratas a fhreagraíonn don API seirbhíse Bosca Wand, и отсылает его. В ответ приходит ссылка на готовую песочницу.

find_program(PYTHON3_EXECUTABLE python3)
if(PYTHON3_EXECUTABLE)
    set(WANDBOX_URL "https://wandbox.org/api/compile.json")

    add_custom_target(wandbox
        COMMAND
            ${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include |
            curl -H "Content-type: application/json" -d @- ${WANDBOX_URL}
        WORKING_DIRECTORY
            ${CMAKE_CURRENT_SOURCE_DIR}
        DEPENDS
            mylib-unit-tests
    )
else()
    message(WARNING "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()

Tionscadal lasmuigh

Anois, déanaimis féachaint ar conas é seo go léir a úsáid.

Tionól

Сборка данного проекта, как и любого другого проекта на системе сборки CMake, состоит из двух этапов:

Giniúint

cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Если команда выше не сработала из-за старой версии CMake, попробуйте опустить -S:

cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Tuilleadh faoi roghanna.

Tógáil an tionscadail

cmake --build путь/к/сборочной/директории [--target target]

Tuilleadh faoi spriocanna tionóil.

Roghanna

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]

Áirítear sprioc coverage, leis ar féidir leat tosú ag tomhas clúdach cód le tástálacha.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]

Предоставляет возможность выключить сборку модульных тестов и цель check. Как следствие, выключается замер покрытия кода тестами (см. MYLIB_COVERAGE).

Также тестирование автоматически отключается в случае, если проект подключается в другой проект качестве подпроекта с помощью команды add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]

Aistríonn sé teanga an doiciméid a ghineann an sprioc doc don cheann tugtha. Le haghaidh liosta de na teangacha atá ar fáil, féach An láithreán gréasáin córas doxygen.

Tá Rúisis cumasaithe de réir réamhshocraithe.

Spriocanna an Tionóil

De réir réamhshocraithe

cmake --build path/to/build/directory
cmake --build path/to/build/directory --target all

Mura bhfuil an sprioc sonraithe (atá comhionann leis an sprioc all), bailíonn sé gach rud is féidir, agus glaonna ar an sprioc freisin check.

mylib-aonad-tástálacha

cmake --build path/to/build/directory --target mylib-unit-tests

Tiomsaíonn tástálacha aonaid. Cumasaithe de réir réamhshocraithe.

seiceáil

cmake --build путь/к/сборочной/директории --target check

Запускает собранные (собирает, если ещё не) модульные тесты. Включено по умолчанию.

См. также mylib-unit-tests.

clúdach

cmake --build путь/к/сборочной/директории --target coverage

Анализирует запущенные (запускает, если ещё не) модульные тесты на предмет покрытия кода тестами при помощи программы gcovr.

Breathnóidh an sciath sceite rud éigin mar seo:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

Níl an sprioc ar fáil ach amháin nuair a bhíonn an rogha cumasaithe MYLIB_COVERAGE.

См. также check.

duga

cmake --build путь/к/сборочной/директории --target doc

Tosaíonn giniúint doiciméadú cód ag baint úsáide as an gcóras Docsaigin.

bosca slaite

cmake --build путь/к/сборочной/директории --target wandbox

Breathnaíonn an freagra ón tseirbhís rud éigin mar seo:

{
    "permlink" :    "QElvxuMzHgL9fqci",
    "status" :  "0",
    "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}

Úsáidtear an tseirbhís chuige seo Bosca Wand. Не знаю, насколько у них резиновые сервера, но думаю, что злоупотреблять данной возможностью не стоит.

Примеры

Tóg an tionscadal i mód dífhabhtaithe le tomhas clúdach

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16

Tionscadal a shuiteáil gan réamhthionól agus tástáil

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target install

Tóg i mód scaoileadh le tiomsaitheoir ar leith

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4

Doiciméid a ghiniúint i mbéarla

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc

Uirlisí

  1. CMake 3.13

    На самом деле версия CMake 3.13 требуется только для запуска некоторых консольных команд, описанных в данной справке. С точки зрения синтаксиса CMake-скриптов достаточно версии 3.8, если генерацию вызывать другими способами.

  2. Leabharlann tástála dochtúr

    Is féidir tástáil a dhíchumasú (féach опцию MYLIB_TESTING).

  3. Docsaigin

    Chun an teanga ina gineadh an doiciméadú a athrú, cuirtear rogha ar fáil MYLIB_DOXYGEN_LANGUAGE.

  4. Ateangaire teanga Python 3

    Le haghaidh giniúna uathoibríoch boscaí gainimh ar líne.

Anailís statach

С помощью CMake и пары хороших инструментов можно обеспечить статический анализ с минимальными телодвижениями.

Seiceáil cpp

Tá tacaíocht ionsuite ag CMake d'uirlis anailíse statach Seiceáil cpp.

Chun seo a dhéanamh ní mór duit an rogha a úsáid CMAKE_CXX_CPPCHECK:

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"

После этого статический анализ будет автоматически запускаться каждый раз во время компиляции и перекомпиляции исходников. Ничего дополнительного делать не нужно.

Clag

Le cabhair ó uirlis iontach scan-build Is féidir leat anailís statach a rith in am ar bith freisin:

scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директории

Здесь, в отличие от случая с Cppcheck, требуется каждый раз запускать сборку через scan-build.

Afterword

CMake — очень мощная и гибкая система, позволяющая реализовывать функциональность на любой вкус и цвет. И, хотя, синтаксис порой оставляет желать лучшего, всё же не так страшен чёрт, как его малюют. Пользуйтесь системой сборки CMake на благо общества и с пользой для здоровья.

Íosluchtaigh múnla tionscadal teachín

Foinse: will.com

Add a comment