Организация проекта 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 ++ Testing Framework для модульного тестирования моего кода, поскольку он кажется довольно простым в использовании. Вы предлагаете связать это с моим кодом, например, в папке 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каталоги и. Выполняется ли сборка в каталоге сборки, а затем двоичные файлы перемещаются в каталог bin? Будут ли двоичные файлы для тестов и библиотека жить в одном месте? Или было бы разумнее структурировать его следующим образом:

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

Я также хотел бы использовать doxygen для документирования своего кода. Можно ли заставить это автоматически запускаться с помощью cmake и make?

Извините за так много вопросов, но я не нашел книги по C ++, которая бы давала удовлетворительные ответы на эти вопросы.

rozzy
источник
6
Отличный вопрос, но я не думаю, что он подходит для формата вопросов и ответов Stack Overflow . Хотя мне очень интересен ответ. +1 и
избранное
1
Это очень много вопросов. Может лучше разбить его на несколько более мелких вопросов и разместить ссылки друг на друга. В любом случае, чтобы ответить на последнюю часть: с помощью CMake вы можете создавать внутри и вне вашего каталога src (я бы рекомендовал снаружи). И да, вы можете использовать doxygen с CMake автоматически.
mistapink

Ответы:

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 файлами. Возможно, даже их собственные запуски и выходы doxygen (объединение нескольких проектов doxygen возможно, но не просто или красиво).

У вас получится что-то вроде этого:

└── 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) настраивает тестовые исполняемые файлы и тестовые примеры

Если у вас есть подкомпоненты, я бы предложил добавить еще одну иерархию и использовать дерево выше для каждого подпроекта. Тогда все становится сложно, потому что вам нужно решить, будут ли подкомпоненты искать и настраивать свои зависимости или вы делаете это на верхнем уровне. Это следует решать в индивидуальном порядке.

Doxygen: после того, как вам удалось пройти через танец конфигурации doxygen, легко использовать CMake add_custom_commandдля добавления цели документа.

Так заканчиваются мои проекты, и я видел несколько очень похожих проектов, но, конечно, это не панацея.

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

Если вы создаете библиотеки, вам также понадобится определение экспорта, чтобы правильно различать компиляторы, например, __declspecв MSVC и visibilityатрибутах в GCC / clang.

PMR
источник
2
Хороший ответ, но я до сих пор не понимаю, почему вам нужно помещать файлы заголовков в дополнительный подкаталог с именем проекта: "/prj/include/prj/foo.hpp", который мне кажется избыточным. Почему не просто "/prj/include/foo.hpp"? Я предполагаю, что у вас будет возможность перенастроить установочные каталоги во время установки, чтобы вы получили <INSTALL_DIR> /include/prj/foo.hpp при установке, или это сложно в CMake?
Уильям Пейн,
6
@William На самом деле это сложно сделать с CPack. Кроме того, как будут выглядеть ваши включения внутри исходных файлов? Если в установленной версии это просто header.hpp, то в пути include должен быть / 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 есть команда для установки файлов (неявно создает цель «install», чтобы выполнить «make install»), которая вы можете использовать, чтобы поместить все заголовки в /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 ++ Testing Framework для модульного тестирования моего кода, поскольку он кажется довольно простым в использовании. Вы предлагаете связать это с моим кодом, например, в папке «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 должны отражать подкаталоги вашего каталога include, это просто для того, чтобы вещи были интуитивно понятными (также постарайтесь, чтобы структура подкаталогов была достаточно плоской (неглубокой), потому что глубокая вложенность папок часто доставляет больше хлопот, чем что-либо другое). Во-вторых, каталог include - это просто каталог установки, его содержимое - это все заголовки, выбранные из каталога src.

В-третьих, система CMake предназначена для распределения по исходным подкаталогам, а не как один файл CMakeLists.txt на верхнем уровне. Благодаря этому вещи остаются локальными, и это хорошо (в духе разделения на независимые части). Если вы добавляете новый источник, новый заголовок или новую тестовую программу, все, что вам нужно, это отредактировать один небольшой и простой файл CMakeLists.txt в соответствующем подкаталоге, не затрагивая ничего другого. Это также позволяет легко реструктурировать каталоги (списки CMakeList являются локальными и содержатся в перемещаемых подкаталогах). CMakeLists верхнего уровня должен содержать большинство конфигураций верхнего уровня, таких как настройка каталогов назначения, пользовательские команды (или макросы) и поиск пакетов, установленных в системе. CMakeLists нижнего уровня должны содержать только простые списки заголовков, источников,

Цитата: Как должен выглядеть файл CMakeLists.txt, чтобы он мог либо собирать только библиотеку, либо библиотеку и тесты?

Основной ответ заключается в том, что CMake позволяет вам специально исключать определенные цели из «всех» (что и создается при вводе «make»), а также вы можете создавать определенные пакеты целей. Я не могу сделать здесь учебник по CMake, но это довольно просто выяснить самому. Однако в этом конкретном случае рекомендуемым решением, конечно же, является использование CTest, который представляет собой просто дополнительный набор команд, которые вы можете использовать в файлах CMakeLists для регистрации ряда целей (программ), которые помечены как единичные. тесты. Итак, CMake поместит все тесты в специальную категорию сборок, и это именно то, о чем вы просили, поэтому проблема решена.

Цитата: Также я видел довольно много проектов, в которых есть сборка и каталог bin. Выполняется ли сборка в каталоге сборки, а затем двоичные файлы перемещаются в каталог bin? Будут ли двоичные файлы для тестов и библиотека жить в одном месте? Или было бы разумнее структурировать его следующим образом:

Наличие каталога сборки вне исходного кода (сборка «вне исходного кода») - это действительно единственное разумное решение, которое в наши дни является стандартом де-факто. Так что определенно создайте отдельный каталог «build» вне исходного каталога, как рекомендуют специалисты CMake и как это делает каждый программист, которого я когда-либо встречал. Что касается каталога bin, ну, это соглашение, и, вероятно, будет хорошей идеей придерживаться его, как я сказал в начале этого поста.

Цитата: Я также хотел бы использовать doxygen для документирования моего кода. Можно ли заставить это автоматически запускаться с помощью cmake и make?

Да. Это более чем возможно, это круто. В зависимости от того, насколько нарядно вы хотите выглядеть, есть несколько возможностей. CMake имеет модуль для Doxygen (т.е. find_package(Doxygen)), который позволяет вам регистрировать цели, которые будут запускать Doxygen для некоторых файлов. Если вы хотите делать более необычные вещи, например, обновлять номер версии в Doxyfile или автоматически вводить дату / дату / дату для исходных файлов и т. Д., Все это возможно с помощью небольшого количества CMake kung-fu. Как правило, для этого необходимо сохранить исходный Doxyfile (например, «Doxyfile.in», который я поместил в макет папки выше), в котором есть токены, которые необходимо найти и заменить командами синтаксического анализа CMake. В моем файле CMakeLists верхнего уровня вы найдете один такой кусок CMake kung-fu, который вместе с 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 ++ Testing Framework для модульного тестирования моего кода, поскольку он кажется довольно простым в использовании.

Gtest действительно прост в использовании и довольно обширен с точки зрения своих возможностей. Его можно очень легко использовать вместе с gmock, чтобы расширить его возможности, но мой собственный опыт работы с 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». Если указать библиотеку как зависимость от тестового exe, сборка тестового exe автоматически приведет к перестройке библиотеки, если она устарела.

Также я видел довольно много проектов, в которых есть сборка и каталог bin. Выполняется ли сборка в каталоге сборки, а затем двоичные файлы перемещаются в каталог bin? Будут ли двоичные файлы для тестов и библиотека жить в одном месте?

CMake рекомендует сборки "вне исходного кода", то есть вы создаете свой собственный каталог сборки вне проекта и запускаете CMake оттуда. Это позволяет избежать "загрязнения" исходного дерева файлами сборки и очень желательно, если вы используете vcs.

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

Что касается собственной поддержки CMake для поиска и выполнения тестов gtest , это будет в значительной степени неуместным, если вы создаете gtest как часть своего проекта. FindGtestМодуль действительно предназначен для использования в случае , когда GTEST был построен отдельно за пределами вашего проекта.

CMake предоставляет свою собственную тестовую среду (CTest), и в идеале каждый случай gtest должен быть добавлен как случай CTest.

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

Я знаю, что у этой проблемы нет простого решения. Самый надежный способ получить список gtest-случаев - выполнить тестовый exe с флагом --gtest_list_tests. Однако это можно сделать только после сборки exe, поэтому CMake не может это использовать. Что оставляет вам два выбора; CMake должен попытаться проанализировать код C ++, чтобы вывести имена тестов (нетривиально в крайнем случае, если вы хотите учесть все макросы gtest, закомментированные тесты, отключенные тесты), или тестовые примеры добавляются вручную в Файл CMakeLists.txt.

Я также хотел бы использовать doxygen для документирования своего кода. Можно ли заставить это автоматически запускаться с помощью cmake и make?

Да, хотя у меня нет опыта в этом отношении. CMake предоставляет FindDoxygenдля этого.

Фрейзер
источник
6

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


обоснование

Для модульности и ремонтопригодности проект организован как набор небольших модулей. Для ясности назовем их 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]

К различным компонентам:

  • Мне нравится, когда source ( .cpp) и headers ( .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 Test проще всего, если его источник находится где-то в вашем исходном каталоге, но вам не обязательно добавлять его туда самостоятельно. Я использую этот проект для его автоматической загрузки, и я заключил его в функцию, чтобы убедиться, что он загружается только один раз, даже если у нас есть несколько тестовых целей.

Эта функция 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! Полезно! :)
Tanasis