C++ і CMake - брати навіки, частина II

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

Що й потрібно було отримати.

Джерело: habr.com

Додати коментар або відгук