Организация проекта на С++ (с gtest, cmake и doxygen)

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

В настоящее время у меня есть только два файла vector3.hpp и vector3.cpp. Этот проект постепенно начнет расти (делая его гораздо более общей библиотекой линейных алгебр), поскольку я больше знаком со всем, поэтому я хотел бы принять "стандартный" макет проекта, чтобы облегчить жизнь позже. Поэтому, оглядевшись, я нашел два способа организовать файлы hpp и cpp, первый из которых:

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

а второе:

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

Что бы вы порекомендовали и почему?

Во-вторых, я хотел бы использовать платформу тестирования Google С++ для модульного тестирования моего кода, поскольку он кажется довольно простым в использовании. Вы предлагаете связать это с моим кодом, например, в папке inc/gtest или contrib/gtest? Если в комплекте вы предлагаете использовать fuse_gtest_files.py script для уменьшения числа или файлов или оставить его как есть? Если не указано, как обрабатывается эта зависимость?

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

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

Ответ 1

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

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

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

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

Связанные зависимости: я думаю, что это во многом зависит от способности система сборки для поиска и настройки зависимостей и способов зависит ваш код от одной версии. Это также зависит от того, как могут быть вашими пользователями и насколько легко установить зависимость от их Платформа. CMake поставляется с find_package script для Google Контрольная работа. Это упрощает работу. Я бы пошел только с комплектом когда это необходимо, и избегать этого в противном случае.

Как построить: Избегайте встроенных сборок. 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, чтобы добавить doc.

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

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

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

Ответ 2

В качестве стартера есть несколько обычных имен для каталогов, которые вы не можете игнорировать, они основаны на давней традиции с файловой системой 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 имеет команду для установки файлов (неявно создает цель "установить", чтобы сделать "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 С++ Testing Framework для модульного тестирования моего кода, поскольку он выглядит довольно простым в использовании. Предлагаете ли вы связать это с моим кодом, например, в папке "inc/gtest" или "contrib/gtest"? Если в комплекте, предложите ли вы использовать fuse_gtest_files.py script для уменьшения числа или файлов или оставить его как есть? Если не указано, как обрабатывается эта зависимость?

Не связывайте зависимости с вашей библиотекой. Это, как правило, довольно ужасная идея, и я всегда ненавижу ее, когда я застреваю, пытаясь создать библиотеку, которая это сделала. Это должно быть ваше последнее средство, и остерегайтесь подводных камней. Часто люди связывают зависимости со своей библиотекой либо потому, что они нацелены на ужасную среду разработки (например, Windows), либо потому, что они поддерживают только старую (устаревшую) версию библиотеки (зависимости), о которой идет речь. Основная ошибка заключается в том, что связанная с вами зависимость может столкнуться с уже установленными версиями одной и той же библиотеки/приложения (например, вы связали gtest, но у человека, пытающегося создать вашу библиотеку, уже установлена ​​более новая (или более старая) версия gtest, а затем два могут столкнуться и дать этому человеку очень неприятную головную боль). Поэтому, как я уже сказал, делайте это на свой страх и риск, и я бы сказал только в крайнем случае. Просить людей установить несколько зависимостей, прежде чем компилировать вашу библиотеку, гораздо меньше, чем пытаться разрешить конфликты между вашими зависимостями и существующими установками.

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

Один файл cpp для каждого класса (или небольшая сплоченная группа классов и функций) является более обычным и практичным, на мой взгляд. Однако, определенно, не компилируйте их всех в один двоичный код, чтобы "все они могли работать вместе". Это действительно плохая идея. Как правило, когда дело доходит до кодирования, вы хотите разделить вещи настолько, насколько это разумно сделать. В случае модульных тестов вы не хотите, чтобы один из двоичных файлов выполнял все тесты, потому что это означает, что любое небольшое изменение, которое вы делаете на что-либо в своей библиотеке, вероятно, вызовет почти полную перекомпиляцию этой программы единичного тестирования, и что только минуты/часы потеряны, ожидая перекомпиляции. Просто придерживайтесь простой схемы: 1 единица = 1 единичная программа. Затем используйте либо script, либо платформу единичного тестирования (например, gtest и/или CTest), чтобы запустить все тестовые программы и сообщить об ошибках/успехах.

Цитата: Поскольку библиотека 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 в соответствующем подкаталоге, не затрагивая ничего другого. Это также позволяет с легкостью реструктурировать каталоги (CMakeLists являются локальными и содержатся в перемещаемых подкаталогах). CMakeLists верхнего уровня должны содержать большинство конфигураций верхнего уровня, таких как настройка целевых каталогов, пользовательских команд (или макросов) и поиск пакетов, установленных в системе. В CMakeLists нижнего уровня должны содержаться только простые списки заголовков, источников и источников единичного тестирования и команды cmake, которые регистрируют их для целей компиляции.

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

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

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

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

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

Да. Это более чем возможно, это потрясающе. В зависимости от того, насколько вы хотите получить удовольствие, есть несколько возможностей. У CMake есть модуль для Doxygen (т.е. find_package(Doxygen)), который позволяет вам регистрировать цели, которые будут запускать Doxygen в некоторых файлах. Если вы хотите сделать больше причудливых вещей, например, обновить номер версии в Doxyfile или автоматически ввести отметки даты/автора для исходных файлов и т.д., Все это возможно с небольшим количеством CMake kung-fu. Как правило, выполнение этого будет заключаться в том, что вы сохраняете исходный Doxyfile (например, "Doxyfile.in", который я вложил в макет папки выше), в котором есть метки, которые должны быть найдены и заменены командами синтаксического анализа CMake. В моем файле CMakeLists верхнего уровня вы найдете один такой кусок CMake kung-fu, который делает несколько модных вещей с cmake-doxygen вместе.

Ответ 3

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

Я бы в целом одобрил следующее:

├── 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 С++ для модульного тестирования моего кода, поскольку он кажется довольно простым в использовании.

Gtest действительно прост в использовании и достаточно всеобъемлющий с точки зрения его возможностей. Его можно использовать рядом с gmock очень легко расширить свои возможности, но мой собственный опыт работы с gmock был менее благоприятным. Я вполне согласен с тем, что это вполне может быть связано с моими собственными недостатками, но тесты gmock, как правило, сложнее создавать и гораздо более хрупкими/сложными в обслуживании. Большой гвоздь в гробу gmock состоит в том, что он действительно не играет хорошо с умными указателями.

Это очень тривиальный и субъективный ответ на огромный вопрос (который, вероятно, на самом деле не принадлежит S.O.)

Вы предлагаете связать это с моим кодом, например, в папке "inc/gtest" или "contrib/gtest"? Если в комплекте, предложите ли вы использовать fuse_gtest_files.py script для уменьшения числа или файлов или оставить его как есть? Если не указано, как обрабатывается эта зависимость?

Я предпочитаю использовать модуль CMake ExternalProject_Add. Это позволяет избежать необходимости хранить исходный код в вашем репозитории или устанавливать его в любом месте. Он автоматически загружается и создается в вашем дереве сборки.

См. мой ответ, посвященный специфике здесь.

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

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


Строительство

Я поклонник CMake, но, как и ваши тестовые вопросы, S.O. вероятно, не лучшее место, чтобы просить мнения по такой субъективной проблеме.

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

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

Библиотека будет отображаться в качестве целевой "ProjectLibrary", а набор тестов - целевой "ProjectTest". Указав библиотеку как зависимость тестового exe, построение test exe автоматически приведет к перестройке библиотеки, если она устарела.

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

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

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

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

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

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

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

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

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

Ответ 4

В дополнение к другим (отличным) ответам, я собираюсь описать структуру, которую я использовал для относительно крупномасштабных проектов.
Я не собираюсь обращаться к подвопросу о 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]

Для разных компонентов:

  • Мне нравится иметь источник (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 bsplinesTests
            COMMAND bsplinesTests
    )
    

Использование 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)