C++ и CMake — братья навек, часть II

от автора

Дружба навек

В предыдущей части данного занимательного рассказа говорилось об организации заголовочной библиотеки в рамках генератора систем сборки CMake.

В этот раз добавим к нему компилируемую библиотеку, а также поговорим о компоновке модулей друг с другом.

Как и прежде, тем, кому нетерпится, могут сразу перейти в обновлённый репозиторий и потрогать всё своими руками.


Содержание

  1. Разделяй
  2. Властвуй

Разделяй

Первое, что нужно сделать для достижения нашей высокой цели — это разделить разрабатываемое ПО на универсальные изолированные блоки, единообразные с точки зрения пользователя.

В первой части был описан такой стандартный блок — проект с заголовочной библиотекой. Теперь же добавим в наш проект компилируемую библиотеку.

Для этого вынесем реализацию функции myfunc в отдельный .cpp-файл:

diff --git a/include/mylib/myfeature.hpp b/include/mylib/myfeature.hpp index 43db388..ba62b4f 100644 --- a/include/mylib/myfeature.hpp +++ b/include/mylib/myfeature.hpp @@ -46,8 +46,5 @@ namespace mylib           \~  \see mystruct       */ -    inline bool myfunc (mystruct) -    { -        return true; -    } +    bool myfunc (mystruct);  } diff --git a/src/mylib/myfeature.cpp b/src/mylib/myfeature.cpp new file mode 100644 index 0000000..abb5004 --- /dev/null +++ b/src/mylib/myfeature.cpp @@ -0,0 +1,9 @@ +#include <mylib/myfeature.hpp> + +namespace mylib +{ +    bool myfunc (mystruct) +    { +        return true; +    } +}

Затем определим компилируемую библиотеку (myfeature), которая будет состоять из полученного на предыдущем шаге .cpp-файла. Новой библиотеке, очевидно, требуются уже имеющиеся заголовки, и для того, чтобы это обеспечить, можно и нужно провязать её с имеющейся целью mylib. Причём провязка между ними публичная, а это значит, что всё, к чему будет подключена цель myfeature, автоматически получит в нагрузку и цель mylib (подробнее про способы провязки).

diff --git a/CMakeLists.txt b/CMakeLists.txt index 108045c..0de77b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,17 @@ target_compile_features(mylib INTERFACE cxx_std_17)   add_library(Mylib::mylib ALIAS mylib)  +################################################################################################### +## +##      Компилируемая библиотека +## +################################################################################################### + +add_library(myfeature src/mylib/myfeature.cpp) +target_link_libraries(myfeature PUBLIC mylib) + +add_library(Mylib::myfeature ALIAS myfeature) +

Далее сделаем, чтобы новая библиотека тоже устанавливалась в систему:

@@ -72,7 +83,7 @@ add_library(Mylib::mylib ALIAS mylib)   install(DIRECTORY include/mylib DESTINATION include)  -install(TARGETS mylib EXPORT MylibConfig) +install(TARGETS mylib myfeature EXPORT MylibConfig)  install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)   include(CMakePackageConfigHelpers)

Нужно обратить внимание, что для цели myfeature, как и для mylib был заведён псевдоним с префиксом Mylib::. То же самое прописано для обеих целей при экспортировании их для установки в систему. Это даёт возможность единообразной работы с целями при любой схеме связывания.

После этого осталось провязать модульные тесты с новой библиотекой (функцию myfunc вынесли из заголовка, так что теперь нужно линковаться):

diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5620be4..bc1266c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,7 +4,7 @@ add_executable(mylib-unit-tests test_main.cpp)  target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)  target_link_libraries(mylib-unit-tests      PRIVATE -        Mylib::mylib +        Mylib::myfeature          doctest::doctest  )

Заголовки (Mylib::mylib) теперь отдельно подключать не нужно, потому что, как уже было сказано, они автоматически подключаются вместе с библиотекой (Mylib::myfeature).

И добавим пару нюансов, чтобы обеспечить замеры покрытия с учётом новоприбывшей библиотеки:

@@ -15,11 +15,16 @@ if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)      target_compile_options(mylib-unit-tests PRIVATE --coverage)      target_link_libraries(mylib-unit-tests PRIVATE gcov)  +    target_compile_options(myfeature PRIVATE --coverage) +    target_link_libraries(myfeature PRIVATE gcov) +      add_custom_target(coverage          COMMAND              ${GCOVR_EXECUTABLE} -                --root=${PROJECT_SOURCE_DIR}/include/ -                --object-directory=${CMAKE_CURRENT_BINARY_DIR} +                --root=${PROJECT_SOURCE_DIR}/ +                --filter=${PROJECT_SOURCE_DIR}/include +                --filter=${PROJECT_SOURCE_DIR}/src +                --object-directory=${PROJECT_BINARY_DIR}          DEPENDS              check      )

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

Властвуй

Теперь у нас есть стандартные модули-блоки, и мы можем властвовать над ними: составлять из них структуру любой сложности, устанавливая их в систему или связывая между собой в рамках единой системы сборки.

Установка в систему

Один из вариантов использования модуля — установить наш модуль в систему.

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

После этого он подключается в любой другой проект с помощью команды find_package.

find_package(Mylib 1.0 REQUIRED)

Подключение в качестве подмодуля

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

Использование

Способы связывания разные, но результат один и тот же. И в одном, и в другом случае в проекте, использующем наш модуль, будут доступны цели Mylib::myfeature и Mylib::mylib, которые могут использоваться, например, так:

add_executable(some_executable some.cpp sources.cpp) target_link_libraries(some_executable PRIVATE Mylib::myfeature)

Конкретно в нашем случае библиотеку Mylib::myfeature нужно подключать тогда, когда необходимо слинковаться с библиотекой libmyfeature. Если достаточно заголовков, то тогда стоит использовать библиотеку Mylib::mylib.

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

Что и требовалось получить.


ссылка на оригинал статьи https://habr.com/ru/post/463295/