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-цели могут быть хитрыми, например, предназначенными только для того, чтобы пробрасывать какие-либо свойства, зависимости и т.п. При этом работа с ними происходит единым образом.


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

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 19

      0
      Блин, туториалы по cmake еще непонятнее, чем официальная справка
        0

        Официальная справка очень даже неплохая.
        Если нужно что-то конкретное разъяснить — спрашивайте.

          0
          Вот я хочу сделать модульную структуру для игрового движка: кидаешь в корневую директорию набор папок, в каждой папке своя библиотека. У каждой библиотеки свой CMakeLists.txt с зависимостями от других библиотек и в корневой директории CMakeLists.txt в котором просто набор add_subdirectory().
          Так вот движок может быть разных версий и библиотеки могут предназначаться для разных версий движка. Т.е. при использовании add_subdirectory() нет контроля за версиями и нужно оформлять в виде пакетов и подключать через find_package() и тут я запнулся, ничего понятно. Какие-то разные способы создания пакетов, какие-то конфиги, нигде нет нормального коротенького примера. Еще вопрос по поводу install. Я так понял, он ставит библиотеку в систему, но зачем мне загаживать систему, если все библиотеки используются в пределах одного проекта.
            0

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

              0
              Как опциональные модули, кидаешь нужные в папочку, все вместе компилируется, получаем либу с нужным набором функций. Завязывать на гит не охота, чтобы не лишаться возможности собрать все вместе локально
                0

                А как гит может помешать собрать всё локально?

                  0
                  Ну допустим у меня архивчик с модулем, который я хочу добавить в общую массу, мне загонять его в репозиторий придется, что cmake его по общей схеме опять вытянул?
                0
                > Если это просто куски проекта, то, вроде, нет необходимости следить за версиями, поскольку и так всё лежит под контролем версий.

                модули могут быть из разных источников и под разные версии движка
                  0

                  Всё это выглядит для меня странно, но могу предложить таки сделать папки полноценными CMake-модулями, которые будут устанавливаться. Просто прописать отдельный путь -DCMAKE_INSTALL_PREFIX=path/to/install, чтобы не захламлять систему.

                    0
                    Ну я на крайняк могу ставить в cmake_binary_dir, осталось выяснить как делать полноценные cmake модули. Такой туториал я как раз и гуглил
                      0

                      Ну вот вы его и нашли. Начинать лучше отсюда: https://habr.com/ru/post/461817/

                        0
                        Я не люблю критиковать чужую работу, но не нашел
                          0

                          Вот готовый шаблон: https://github.com/izvolov/mylib

                            0

                            Да я видел уже. Там тоже есть вопросы. Если уж оформлять mylib как отдельную библиотеку, то очевидно ее .h и .cpp должны быть отделены от общей мешанины, а в .cpp должно быть обращение к своим заголовочным файлам как #include "", как это во всех библиотеках сделано. И только когда заголовки проинсталлятся, другие библиотеки/приложения будут обращаться к этим заголовкам через #include <>.

                              0

                              В директории src можно делать что угодно, т.к. это скрытая часть. В интерфейсе, то есть директории include, нужно обращаться по <>.


                              В любом случае, не вижу проблемы сделать так, как вам нужно.

                                0
                                В интерфейсе, то есть директории include нужно обращаться по <>.

                                Нет. В директории include к другим заголовкам из той же библиотеки тоже нужно обращаться через "".

                                  0

                                  Делайте, как хотите. К CMake это отношения не имеет.

                                    0

                                    Это обосновывает процесс установки. При разработке изолированной библиотеки не будешь же каждый раз при отладке ставить заголовки, поэтому обращение обязательно через "". А чтобы другие либы/приложение могли использовать эту либу, ее надо ставить, и тогда они подключают заголовки через <>.

                                      0

                                      CMake способен правильно обрабатывать ситуацию с <> даже при разработке изолированной библиотеки.


                                      См. инструкцию и проект-шаблон.

        Only users with full accounts can post comments. Log in, please.