C ++ проектна організація (з gtest, cmake та doxygen)


123

Я взагалі новачок у програмуванні, тому вирішив, що почну з створення простого векторного класу в C ++. Однак я хотів би зайнятися хорошими звичками з самого початку, а не намагатися змінити свій робочий процес згодом.

Наразі у мене є лише два файли vector3.hppі vector3.cpp. Цей проект поступово почне розростатися (що робить його набагато більше загальною бібліотекою лінійної алгебри), коли я все більше ознайомлююся, тому я хотів би прийняти "стандартний" макет проекту, щоб згодом полегшити життя. Отож, оглянувшись навколо, я знайшов два способи впорядкувати файли hpp та cpp, перший:

project
└── src
    ├── vector3.hpp
    └── vector3.cpp

а друга істота:

project
├── inc
   └── project
       └── vector3.hpp
└── src
    └── vector3.cpp

Що б ви порадили і чому?

По-друге, я хотів би використовувати рамку тестування Google C ++ для тестування свого коду, оскільки це здається досить простим у використанні. Чи пропонуєте ви поєднати це з моїм кодом, наприклад, у папці inc/gtestабо contrib/gtest? Якщо ви в комплекті, чи пропонуєте ви використовувати fuse_gtest_files.pyскрипт, щоб зменшити кількість чи файли чи залишити його таким, яким він є? Якщо не в комплекті, як обробляється ця залежність?

Що стосується написання тестів, як вони, як правило, організовані? Я думав мати один cpp-файл для кожного класу ( test_vector3.cppнаприклад), але все зібрано в один двійковий файл, щоб їх можна було легко запускати разом?

Оскільки бібліотека gtest, як правило, будується за допомогою cmake та make, я думав, що є сенс, щоб мій проект також будувався таким чином? Якщо я вирішив використовувати наступний макет проекту:

├── CMakeLists.txt
├── contrib
   └── gtest
       ├── gtest-all.cc
       └── gtest.h
├── docs
   └── Doxyfile
├── inc
   └── project
       └── vector3.cpp
├── src
   └── vector3.cpp
└── test
    └── test_vector3.cpp

Як це CMakeLists.txtповинно виглядати, щоб воно могло будувати лише бібліотеку, або бібліотеку, і тести? Також я бачив досить багато проектів, які мають buildі binкаталог. Чи відбувається збірка в каталозі збірки, а потім двійкові файли переміщуються до каталогу бін? Чи будуть бінарні файли для тестів і бібліотека жити там же? Або було б більше сенсу структурувати так:

test
├── bin
├── build
└── src
    └── test_vector3.cpp

Я також хотів би використовувати доксиген для документування свого коду. Чи можна змусити це автоматично запускати cmake і make?

Вибачте за стільки питань, але я не знайшов книги на C ++, яка б задовільно відповідала на цей тип питань.


6
Чудове запитання, але я не думаю, що це добре підходить для формату Q&A Stack Overflow . Мені дуже цікава відповідь. +1 & fav
Luchian Grigore

1
Це багато питань на величезному рівні. Нехай краще розділити його на кілька менших питань і розмістити посилання один на одного. Так чи інакше, щоб відповісти на останню частину: За допомогою CMake ви можете створити всередині та зовні ваш каталог src (я рекомендую зовні). І так, ви можете використовувати доксиген із CMake автоматично.
помилка

Відповіді:


84

Системи побудови C ++ - це трохи чорне мистецтво, і чим старший проект, тим дивніші речі ви можете знайти, тому не дивно, що виникає багато питань. Я спробую по черзі розглянути питання та згадати деякі загальні речі щодо створення бібліотек C ++.

Розділення заголовків та файлів cpp у каталогах. Це важливо лише в тому випадку, якщо ви будуєте компонент, який повинен використовуватися як бібліотека на відміну від фактичної програми. Ваші заголовки є основою для взаємодії користувачів із тим, що ви пропонуєте, і їх потрібно встановити. Це означає, що вони повинні бути в підкаталозі (ніхто не хоче, щоб багато заголовків закінчувалося на найвищому рівні /usr/include/), і ваші заголовки повинні мати можливість включити себе до таких налаштувань.

└── prj
    ├── include
       └── prj
           ├── header2.h
           └── header.h
    └── src
        └── x.cpp

добре працює, тому що включайте шляхи розробки, і ви можете використовувати простий глобалізатор для встановлення цілей.

Залежності групи: Я думаю, що це багато в чому залежить від здатності системи побудови знаходити та конфігурувати залежності та наскільки залежно від вашого коду на одній версії. Це також залежить від того, наскільки ваші користувачі здатні та як легко встановити залежність на їх платформі. CMake поставляється із find_packageсценарієм для Google Test. Це значно полегшує справи. Я б ходив із комплектуванням лише в разі необхідності, інакше цього уникати.

Як побудувати: уникайте складання джерел. CMake створює джерела простоти, і це значно полегшує життя.

Я думаю, ви також хочете використовувати CTest для запуску тестів для вашої системи (вона також поставляється із вбудованою підтримкою для GTest). Важливим рішенням для макета каталогів та організації тесту буде: Чи закінчуєте ви підпроекти? Якщо так, то вам потрібно ще трохи попрацювати під час налаштування CMakeLists і слід розділити ваші підпроекти на підкаталоги, кожен зі своїми власними файлами includeта srcфайлами. Можливо, навіть свої власні доксигенні запуски та виходи (можливе поєднання декількох проектів доксигенів, але це не просто і досить).

Ви закінчите щось подібне:

└── prj
    ├── CMakeLists.txt <-- (1)
    ├── include
       └── prj
           ├── header2.hpp
           └── header.hpp
    ├── src
       ├── CMakeLists.txt <-- (2)
       └── x.cpp
    └── test
        ├── CMakeLists.txt <-- (3)
        ├── data
           └── testdata.yyy
        └── testcase.cpp

де

  • (1) налаштовує залежності, специфіку платформи та вихідні шляхи
  • (2) налаштовує бібліотеку, яку ви збираєтеся створити
  • (3) налаштовує тестові виконувані файли та тестові кейси

Якщо у вас є підкомпоненти, я б запропонував додати іншу ієрархію та використовувати дерево вище для кожного підпроекту. Тоді справи стають складними, адже вам потрібно вирішити, чи шукають підрозділи та налаштовують їх залежність, чи ви робите це на найвищому рівні. Це має вирішуватися в кожному конкретному випадку.

Доксиген: Після того, як вам вдалося пройти танець конфігурації доксигену, тривіально використовувати CMake add_custom_commandдля додавання док-мети.

Так закінчуються мої проекти, і я бачив кілька дуже схожих проектів, але, звичайно, це далеко не все.

Додаток У якийсь момент вам потрібно буде генерувати config.hpp файл, який містить визначення версії та, можливо, визначення деякого ідентифікатора контролю версії (номер редакції Git або SVN версія). CMake має модулі для автоматизації пошуку цієї інформації та генерування файлів. Ви можете використовувати CMake configure_fileдля заміни змінних у файлі шаблону змінними, визначеними всередині CMakeLists.txt.

Якщо ви створюєте бібліотеки, вам також знадобиться визначення експорту, щоб отримати різницю між компіляторами, наприклад, __declspecна MSVC та visibilityатрибути GCC / clang.


2
Приємна відповідь, але мені все одно незрозуміло, чому вам потрібно помістити файли заголовків у додатковий підкаталог, названий проектом: "/prj/include/prj/foo.hpp", який мені здається зайвим. Чому б не просто "/prj/include/foo.hpp"? Я припускаю, що у вас буде можливість повторно перемістити каталоги встановлення під час встановлення, тому ви отримаєте <INSTALL_DIR> /include/prj/foo.hpp при встановленні, чи це важко в CMake?
Вільям Пейн

6
@William Це насправді складно зробити з CPack. Крім того, як би виглядали ваші файли, що містяться у вихідних файлах? Якщо вони просто "header.hpp" у встановленій версії, "/ usr / include / prj /" повинен знаходитись у шляху включення, а не просто "/ usr / include".
pmr

37

У якості початківця є деякі звичайні назви каталогів, які ви не можете ігнорувати, вони засновані на багаторічній традиції файлової системи Unix. Це:

trunk
├── bin     : for all executables (applications)
├── lib     : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src     : for source files
└── doc     : for documentation

Це, мабуть, гарна ідея дотримуватися цього базового планування, принаймні на верхньому рівні.

Щодо розділення файлів заголовків та вихідних файлів (cpp), обидві схеми є досить поширеними. Однак я, як правило, вважаю за краще зберігати їх разом, а щоденні завдання просто практичніше мати файли разом. Крім того, коли весь код знаходиться в одній папці верхнього рівня, тобто trunk/src/папці, ви можете помітити, що всі інші папки (bin, lib, include, doc і, можливо, деякі тестові папки) на верхньому рівні, на додаток до каталог "build" для збірки з вихідного коду - це всі папки, які не містять нічого, крім файлів, що генеруються в процесі збирання. Таким чином, потрібно створити резервну копію або набагато краще зберігати лише папку src під системою / сервером управління версіями (як Git або SVN).

Що ж стосується встановлення файлів заголовків у системі призначення (якщо ви хочете врешті-решт розповсюдити свою бібліотеку), добре, CMake має команду для встановлення файлів (неявно створює ціль "встановити", робити "зробити встановлення"), який ви можете використовувати для введення всіх заголовків у /usr/include/каталог. Я просто використовую для цієї мети наступний макрос cmake:

# custom macro to register some headers as target for installation:
#  setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
  foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
    install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
  endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)

Де SRCROOTє змінною cmake, яку я встановив у папку src, і INCLUDEROOTє змінною cmake, яку я налаштовую туди, куди потрібно звертатися до заголовків. Звичайно, є багато інших способів зробити це, і я впевнений, що мій шлях не найкращий. Справа в тому, що немає підстав розділяти заголовки та джерела лише тому, що в цільовій системі потрібно встановлювати лише заголовки, тому що дуже легко, особливо з CMake (або CPack), вибирати та конфігурувати заголовки на встановлюватись, не маючи їх мати в окремому каталозі. І це я бачив у більшості бібліотек.

Цитата: По-друге, я хотів би використовувати рамку тестування Google C ++ для тестування свого коду, оскільки це здається досить простим у використанні. Ви пропонуєте поєднати це з моїм кодом, наприклад, у папці "inc / gtest" або "contrib / gtest"? Якщо ви в комплекті, пропонуєте використовувати скрипт fuse_gtest_files.py, щоб зменшити кількість чи файли або залишити його таким, яким він є? Якщо не в комплекті, як обробляється ця залежність?

Не зв'язуйте залежності зі своєю бібліотекою. Це взагалі досить жахлива ідея, і я завжди ненавиджу її, коли я застрягаю, намагаючись створити бібліотеку, яка це зробила. Це повинно бути вашим останнім засобом, і остерігайтеся підводних каменів. Часто люди поєднують залежності зі своєю бібліотекою або тому, що вони націлені на жахливе середовище розробки (наприклад, Windows), або тому, що вони підтримують лише стару (застарілу) версію бібліотеки (залежності), про яку йдеться. Основна проблема полягає в тому, що ваша пакетна залежність може зіткнутися з уже встановленими версіями тієї самої бібліотеки / програми (наприклад, ви поєднали gtest, але особа, яка намагається створити вашу бібліотеку, вже має нову (або старішу) версію gtest, яка вже встановлена, тоді вони можуть зіткнутися і нанести цій людині дуже неприємний головний біль). Отже, як я вже сказав, робіть це на свій страх і ризик, і я б сказав лише в крайньому випадку. Попросити людей встановити кілька залежностей, перш ніж мати змогу скласти вашу бібліотеку, це набагато менше зла, ніж намагатися вирішити сутички між вашими пакетними залежностями та існуючими установками.

Цитата: Що стосується написання тестів, як вони, як правило, організовані? Я думав мати один файл cpp для кожного класу (наприклад, test_vector3.cpp), але все компільовано в один двійковий файл, щоб їх можна було легко запускати разом?

Один файл cpp на клас (або невелика згуртована група класів та функцій) на мою думку є більш звичним та практичним. Однак, безумовно, не компілюйте їх усіх в один двійковий файл лише для того, щоб "їх можна було працювати разом". Це дійсно погана ідея. Як правило, якщо мова йде про кодування, ви хочете розділити речі настільки, наскільки це розумно зробити. Що стосується одиничних тестів, ви не хочете, щоб один двійковий файл виконував усі тести, тому що це означає, що будь-яка невелика зміна, яку ви внесете до будь-якої вашої бібліотеки, швидше за все, спричинить майже повну перекомпіляцію цієї програми тестування модулів. , і це лише хвилини / години, втрачені в очікуванні перекомпіляції. Просто дотримуйтесь простої схеми: 1 одиниця = 1 одинична програма тестування. Тоді,

Цитата: Оскільки бібліотека gtest, як правило, будується за допомогою cmake і make, я думав, що має сенс мій проект також будуватися таким чином? Якщо я вирішив використовувати наступний макет проекту:

Я б скоріше запропонував цю схему:

trunk
├── bin
├── lib
   └── project
       └── libvector3.so
       └── libvector3.a        products of installation / building
├── docs
   └── Doxyfile
├── include
   └── project
       └── vector3.hpp
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

├── src
   └── CMakeLists.txt
   └── Doxyfile.in
   └── project                 part of version-control / source-distribution
       └── CMakeLists.txt
       └── vector3.hpp
       └── vector3.cpp
       └── test
           └── test_vector3.cpp
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

├── build
└── test                        working directories for building / testing
    └── test_vector3

Тут можна помітити кілька речей. По-перше, підкаталоги вашого каталогу src повинні відображати підкаталоги вашого каталогу включення, це лише для того, щоб все було інтуїтивно зрозумілим (також намагайтеся тримати структуру підкаталогу досить плоскою (неглибокою), оскільки глибоке вкладання папок. часто більше клопоту, ніж будь-що інше). По-друге, каталог "включати" - це лише інсталяційний каталог, його вміст - це те, що заголовки вибрані з каталогу src.

По-третє, система CMake призначена для поширення по вихідних підкаталогах, а не як один файл CMakeLists.txt на верхньому рівні. Це зберігає речі місцеві, і це добре (у дусі розбиття речей на самостійні частини). Якщо ви додасте нове джерело, новий заголовок або нову програму тестування, все, що вам потрібно, - це відредагувати один невеликий і простий файл CMakeLists.txt у відповідному під-каталозі, не зачіпаючи нічого іншого. Це також дозволяє з легкістю реструктурувати каталоги (CMakeLists є локальними та містяться у переміщених підкаталогах). CMakeLists верхнього рівня повинні містити більшість конфігурацій верхнього рівня, таких як налаштування каталогів призначення, користувацькі команди (або макроси) та пошук пакетів, встановлених у системі. CMakeLists нижнього рівня повинні містити лише прості списки заголовків, джерел,

Цитата: Як повинен виглядати CMakeLists.txt, щоб він міг будувати лише бібліотеку, або бібліотеку, і тести?

Основна відповідь полягає в тому, що CMake дозволяє спеціально виключати певні цілі з "all" (це те, що будується під час введення "make"), а також ви можете створювати конкретні пучки цілей. Я не можу тут зробити підручник CMake, але це досить прямо вперед, щоб дізнатися самостійно. Однак у цьому конкретному випадку рекомендованим рішенням є, звичайно, використання CTest, що є лише додатковим набором команд, які ви можете використовувати у файлах CMakeLists для реєстрації ряду цілей (програм), позначених як одиниця- тести. Отже, CMake помістить усі тести в спеціальну категорію збірок, і саме це ви і попросили, тому вирішена проблема.

Цитата: Також я бачив досить багато проектів, у яких є збірка рекламних файлів і каталог бін. Чи відбувається збірка в каталозі збірки, а потім двійкові файли переміщуються до каталогу бін? Чи будуть бінарні файли для тестів і бібліотека жити там же? Або було б більше сенсу структурувати так:

Мати каталог збірки за межами джерела ("out-of-source" build) - це справді єдине розумне, що потрібно зробити, це фактично стандарт сьогодні. Отож, безумовно, є окремий каталог "build" поза каталогом джерел, як рекомендують люди CMake, і як це робить кожен програміст. Що стосується каталогу бін, то це конвенція, і, мабуть, це гарна ідея дотримуватися цього, як я вже говорив на початку цієї публікації.

Цитата: Я також хотів би використовувати доксиген для документування свого коду. Чи можна змусити це автоматично запускати cmake і make?

Так. Це більш ніж можливо, це приголомшливо. Залежно від того, наскільки фантазії ви хочете отримати, є кілька можливостей. CMake має модуль для Doxygen (тобто find_package(Doxygen)), який дозволяє реєструвати цілі, які запускатимуть Doxygen у деяких файлах. Якщо ви хочете зробити більш фантазійні речі, такі як оновлення номера версії в Doxyfile або автоматичне введення позначок дати / автора для вихідних файлів тощо, все можливе за допомогою кунг-фу CMake. Як правило, це призведе до того, що ви зберігаєте вихідний Doxyfile (наприклад, "Doxyfile.in", який я помістив у макет папки вгорі), який повинен знаходити лексеми та замінюватися командами розбору CMake. У моєму файлі CMakeLists вищого рівня ви знайдете одну таку частину кунг-фу CMake, яка разом робить кілька фантазійних речей із cmake-doxygen.


Так main.cppтреба йти trunk/bin?
Уґніус Малукас

17

Структурування проекту

Я, як правило, підтримую таке:

├── CMakeLists.txt
|
├── docs/
│   └── Doxyfile
|
├── include/
│   └── project/
│       └── vector3.hpp
|
├── src/
    └── project/
        └── vector3.cpp
        └── test/
            └── test_vector3.cpp

Це означає, що у вас є дуже чітко визначений набір файлів API для вашої бібліотеки, а структура означає, що клієнти вашої бібліотеки будуть робити

#include "project/vector3.hpp"

а не менш явний

#include "vector3.hpp"


Мені подобається, що структура / src дерева відповідає структурі дерева / include, але це дійсно особисті переваги. Однак якщо ваш проект розширюється, щоб містити підкаталоги в / include / project, це, як правило, допоможе співставити ті, які знаходяться в дереві / src.

Щодо тестів, я віддаю перевагу тому, щоб вони були «близько» до файлів, які вони тестують, і якщо ви закінчуєте підкаталоги в / src, для інших це досить проста парадигма, якщо вони хочуть знайти тестовий код заданого файлу.


Тестування

По-друге, я хотів би використовувати рамку тестування Google C ++ для тестування свого коду, оскільки це здається досить простим у використанні.

Gtest насправді простий у використанні і досить вичерпний з точки зору своїх можливостей. Його можна використовувати поряд з gmock дуже легко для розширення його можливостей, але мій власний досвід роботи з gmock був менш сприятливим. Я цілком готовий прийняти, що це, можливо, зводиться до моїх недоліків, але тести gmock, як правило, складніше створити та набагато більш крихкі / важкі в обслуговуванні. Великий цвях у гробній гробці полягає в тому, що він справді не грає добре з розумними покажчиками.

Це дуже тривіальна і суб'єктивна відповідь на величезне запитання (яке, мабуть, насправді не належить до SO)

Ви пропонуєте поєднати це з моїм кодом, наприклад, у папці "inc / gtest" або "contrib / gtest"? Якщо ви в комплекті, пропонуєте використовувати скрипт fuse_gtest_files.py, щоб зменшити кількість чи файли або залишити його таким, яким він є? Якщо не в комплекті, як обробляється ця залежність?

Я вважаю за краще використовувати ExternalProject_Addмодуль CMake . Це дозволяє уникнути необхідності зберігати вихідний код gtest у вашому сховищі або встановлювати його де завгодно. Він завантажується та вбудовується у дерево вашої збірки автоматично.

Дивіться мою відповідь, що стосується специфіки тут .

Що стосується написання тестів, як вони, як правило, організовані? Я думав мати один файл cpp для кожного класу (наприклад, test_vector3.cpp), але все зібрано в один двійковий файл, щоб їх можна було легко запускати разом?

Хороший план.


Будівництво

Я шанувальник CMake, але як і у ваших питаннях, пов'язаних з тестом, SO - це, мабуть, не найкраще місце для запиту думок щодо такого суб'єктивного питання.

Як повинен виглядати CMakeLists.txt, щоб він міг будувати лише бібліотеку або бібліотеку та тести?

add_library(ProjectLibrary <All library sources and headers>)
add_executable(ProjectTest <All test files>)
target_link_libraries(ProjectTest ProjectLibrary)

Бібліотека відображатиметься як цільова "ProjectLibrary", а тестовий набір - як цільовий "ProjectTest". Вказавши бібліотеку як залежність тестової програми, побудова тестової програми автоматично призведе до відновлення бібліотеки, якщо вона застаріла.

Крім того, я бачив досить багато проектів, які мають каталог складання реклами та бін. Чи відбувається збірка в каталозі збірки, а потім двійкові файли переміщуються до каталогу бін? Чи будуть бінарні файли для тестів і бібліотека жити там же?

CMake рекомендує збірки "out-of-source", тобто ви створюєте власний каталог збірок за межами проекту та запускаєте CMake звідти. Це дозволяє уникнути "забруднення" вашого вихідного дерева файлами збірки, і дуже бажано, якщо ви використовуєте vcs.

Ви можете вказати, що бінарні файли переміщуються або копіюються в інший каталог, щойно побудований, або що вони створені за замовчуванням в іншому каталозі, але в цілому немає потреби. CMake надає вичерпні способи встановлення вашого проекту за бажанням або полегшує для інших проектів CMake "пошук" відповідних файлів вашого проекту.

Що стосується власної підтримки CMake для пошуку та виконання тестів на gtest , то це в значній мірі буде недоцільним, якщо ви будуєте gtest як частину свого проекту. FindGtestМодуль дійсно призначений для використання в разі , коли GTEST був побудований окремо за межами вашого проекту.

CMake надає власну тестову рамку (CTest), і в ідеалі кожен випадковий випадок буде доданий як випадок CTest.

Однак GTEST_ADD_TESTSмакрос, що надає FindGtestможливість легко додавати випадки gtest як окремих випадків ctest, дещо не вистачає в тому, що він не працює для інших макросів gtest, крім TESTта TEST_F. Ціннісно або типу параметризованих тестів з використанням TEST_P, TYPED_TEST_Pі т.д., не обробляються взагалі.

Проблема не має простого рішення, яке я знаю. Найбільш надійний спосіб отримати список випадків виклику - це виконати тестовий екзе з прапором --gtest_list_tests. Однак це можна зробити лише після того, як побудовано exe, тому CMake не може цим скористатися. Що залишає у вас два варіанти; CMake повинен спробувати проаналізувати код C ++, щоб вивести назви тестів (нетривіально в крайньому випадку, якщо ви хочете врахувати всі макроси gtest, тести з коментарями, тести з вимкненням) або тестові приклади додаються вручну до Файл CMakeLists.txt.

Я також хотів би використовувати доксиген для документування свого коду. Чи можна змусити це автоматично запускати cmake і make?

Так - хоча я не маю досвіду на цьому фронті. CMake передбачає FindDoxygenдля цього.


6

На додаток до інших (відмінних) відповідей я збираюся описати структуру, яку я використовував для відносно масштабних проектів.
Я не збираюся займатися питаннями про доксиген, оскільки я б просто повторив те, що сказано в інших відповідях.


Обґрунтування

Для модульності та ремонтопридатності проект організований у вигляді набору невеликих одиниць. Для наочності назвемо їх UnitX із X = A, B, C, ... (але вони можуть мати будь-яку загальну назву). Потім структура каталогів організовується для відображення цього вибору, з можливістю групувати одиниці, якщо це необхідно.

Рішення

Основний макет каталогів наступний (зміст одиниць детально розгорнуто далі):

project
├── CMakeLists.txt
├── UnitA
├── UnitB
├── GroupA
   └── CMakeLists.txt
   └── GroupB
       └── CMakeLists.txt
       └── UnitC
       └── UnitD
   └── UnitE

project/CMakeLists.txt може містити наступне:

cmake_minimum_required(VERSION 3.0.2)
project(project)
enable_testing() # This will be necessary for testing (details below)

add_subdirectory(UnitA)
add_subdirectory(UnitB)
add_subdirectory(GroupA)

і project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

і project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Тепер до структури різних одиниць (візьмемо, як приклад, UnitD)

project/GroupA/GroupB/UnitD
├── README.md
├── CMakeLists.txt
├── lib
   └── CMakeLists.txt
   └── UnitD
       └── ClassA.h
       └── ClassA.cpp
       └── ClassB.h
       └── ClassB.cpp
├── test
   └── CMakeLists.txt
   └── ClassATest.cpp
   └── ClassBTest.cpp
   └── [main.cpp]

До різних компонентів:

  • Мені подобається, що джерело ( .cpp) та заголовки ( .h) в одній папці. Це дозволяє уникнути повторної ієрархії каталогів, полегшує обслуговування. Для встановлення це не проблема (особливо з CMake) просто фільтрувати файли заголовків.
  • Роль каталогу UnitDполягає в тому, щоб згодом дозволити включати файли з #include <UnitD/ClassA.h>. Також при встановленні цього пристрою ви можете просто скопіювати структуру каталогів як є. Зауважте, що ви також можете організувати вихідні файли у підкаталогах.
  • Мені подобається READMEфайл, який підсумовує, про що йдеться в апараті, і вказує корисну інформацію про нього.
  • CMakeLists.txt може просто містити:

    add_subdirectory(lib)
    add_subdirectory(test)
  • lib/CMakeLists.txt:

    project(UnitD)
    
    set(headers
        UnitD/ClassA.h
        UnitD/ClassB.h
        )
    
    set(sources
        UnitD/ClassA.cpp
        UnitD/ClassB.cpp    
        )
    
    add_library(${TARGET_NAME} STATIC ${headers} ${sources})
    
    # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers
    target_include_directories(UnitD
                               PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                               PUBLIC $<INSTALL_INTERFACE:include/SomeDir>
                               )
    
    target_link_libraries(UnitD
                          PUBLIC UnitA
                          PRIVATE UnitC
                          )

    Тут зауважте, що не потрібно говорити CMake, що ми хочемо, щоб включати каталоги для, UnitAі UnitC, як це вже було зазначено під час налаштування цих одиниць. Крім того, PUBLICпокаже всі цілі, від яких залежить, UnitDщо вони повинні автоматично включати UnitAзалежність, при цьому UnitCвони не будуть потрібні ( PRIVATE).

  • test/CMakeLists.txt (див. далі нижче, якщо ви хочете використовувати GTest для цього):

    project(UnitDTests)
    
    add_executable(UnitDTests
                   ClassATest.cpp
                   ClassBTest.cpp
                   [main.cpp]
                   )
    
    target_link_libraries(UnitDTests
                          PUBLIC UnitD
    )
    
    add_test(
            NAME UnitDTests
            COMMAND UnitDTests
    )

Використання GoogleTest

Для тесту Google найпростіше, якщо його джерело є десь у вашому каталозі джерел, але вам не потрібно насправді додавати його туди самостійно. Я використовую цей проект, щоб автоматично його завантажувати, і я запускаю його використання у функцію, щоб переконатися, що він завантажений лише один раз, хоча у нас є кілька тестових цілей.

Ця функція CMake є такою:

function(import_gtest)
  include (DownloadProject)
  if (NOT TARGET gmock_main)
    include(DownloadProject)
    download_project(PROJ                googletest
                     GIT_REPOSITORY      https://github.com/google/googletest.git
                     GIT_TAG             release-1.8.0
                     UPDATE_DISCONNECTED 1
                     )
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
    add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
  endif()
endfunction()

а потім, коли я хочу використовувати його всередині однієї з моїх тестових цілей, я додаю наступні рядки до CMakeLists.txt(це для прикладу вище test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)

Гарний "хак" ви там зробили з Gtest і cmake! Корисно! :)
Танасіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.