Я новичок в программировании в целом, поэтому решил, что начну с создания простого векторного класса на 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 ++, которая бы давала удовлетворительные ответы на эти вопросы.
источник
Ответы:
Системы сборки C ++ - это что-то вроде черного искусства, и чем старше проект, тем более странные вещи вы можете найти, поэтому неудивительно, что возникает много вопросов. Я попытаюсь пройти по вопросам один за другим и упомянуть некоторые общие вещи, касающиеся создания библиотек C ++.
Разделение заголовков и файлов cpp в каталогах. Это важно только в том случае, если вы создаете компонент, который предполагается использовать как библиотеку, а не как фактическое приложение. Ваши заголовки являются основой взаимодействия пользователей с тем, что вы предлагаете, и должны быть установлены. Это означает, что они должны находиться в подкаталоге (никто не хочет, чтобы много заголовков попадали на верхний уровень
/usr/include/
), и ваши заголовки должны иметь возможность включать себя с такой настройкой.работает хорошо, потому что подключаемые пути работают, и вы можете использовать простую подстановку для целей установки.
Объединение зависимостей: я думаю, что это во многом зависит от способности системы сборки определять и настраивать зависимости и от того, насколько ваш код зависит от одной версии. Это также зависит от того, насколько способны ваши пользователи и насколько легко установить зависимость на их платформе. CMake поставляется со
find_package
скриптом для Google Test. Это значительно упрощает работу. Я бы пошел с бандлингом только тогда, когда это необходимо, и избегал бы этого в противном случае.Как строить: избегайте сборок из исходных кодов. CMake упрощает сборку исходного кода и значительно упрощает жизнь.
Я полагаю, вы также хотите использовать CTest для запуска тестов для своей системы (он также имеет встроенную поддержку GTest). Важным решением для макета каталога и организации тестирования будет следующее: останутся ли у вас подпроекты? Если это так, вам потребуется дополнительная работа при настройке CMakeLists, и вам следует разделить ваши подпроекты на подкаталоги, каждый со своими собственными
include
иsrc
файлами. Возможно, даже их собственные запуски и выходы doxygen (объединение нескольких проектов doxygen возможно, но не просто или красиво).У вас получится что-то вроде этого:
где
Если у вас есть подкомпоненты, я бы предложил добавить еще одну иерархию и использовать дерево выше для каждого подпроекта. Тогда все становится сложно, потому что вам нужно решить, будут ли подкомпоненты искать и настраивать свои зависимости или вы делаете это на верхнем уровне. Это следует решать в индивидуальном порядке.
Doxygen: после того, как вам удалось пройти через танец конфигурации doxygen, легко использовать CMake
add_custom_command
для добавления цели документа.Так заканчиваются мои проекты, и я видел несколько очень похожих проектов, но, конечно, это не панацея.
Дополнение. В какой-то момент вы захотите сгенерировать
config.hpp
файл, содержащий определение версии и, возможно, определение для некоторого идентификатора системы управления версиями (хэш Git или номер версии SVN). CMake имеет модули для автоматизации поиска этой информации и создания файлов. Вы можете использовать CMakeconfigure_file
для замены переменных в файле шаблона на переменные, определенные внутриCMakeLists.txt
.Если вы создаете библиотеки, вам также понадобится определение экспорта, чтобы правильно различать компиляторы, например,
__declspec
в MSVC иvisibility
атрибутах в GCC / clang.источник
Для начала, есть несколько обычных имен для каталогов, которые нельзя игнорировать, они основаны на давней традиции файловой системы Unix. Эти:
Вероятно, будет хорошей идеей придерживаться этого базового макета, по крайней мере, на верхнем уровне.
Что касается разделения файлов заголовков и исходных файлов (cpp), обе схемы довольно распространены. Однако я предпочитаю хранить их вместе, просто для повседневных задач удобнее хранить файлы вместе. Кроме того, когда весь код находится в одной папке верхнего уровня, то есть в
trunk/src/
папке, вы можете заметить, что все другие папки (bin, lib, include, doc и, возможно, некоторые тестовые папки) находятся на верхнем уровне, в дополнение к каталог «build» для сборки вне исходного кода - это все папки, которые не содержат ничего, кроме файлов, созданных в процессе сборки. Таким образом, необходимо создать резервную копию только папки src или, что еще лучше, хранить ее в системе / сервере контроля версий (например, Git или SVN).А когда дело доходит до установки ваших файлов заголовков в целевой системе (если вы хотите в конечном итоге распространить свою библиотеку), в CMake есть команда для установки файлов (неявно создает цель «install», чтобы выполнить «make install»), которая вы можете использовать, чтобы поместить все заголовки в
/usr/include/
каталог. Я просто использую для этого следующий макрос cmake:Где
SRCROOT
находится переменная cmake, которую я установил в папку src, иINCLUDEROOT
переменная cmake, которую я настраиваю, куда бы ни направлялись заголовки. Конечно, есть много других способов сделать это, и я уверен, что мой способ не лучший. Дело в том, что нет причин разделять заголовки и источники только потому, что в целевой системе должны быть установлены только заголовки, потому что очень легко, особенно с CMake (или CPack), выбрать и настроить заголовки для быть установленным без необходимости их размещения в отдельном каталоге. И это то, что я видел в большинстве библиотек.Не связывайте зависимости с вашей библиотекой. Как правило, это довольно ужасная идея, и я всегда ненавижу ее, когда я застреваю, пытаясь создать библиотеку, которая сделала бы это. Это должно быть вашим последним средством и остерегайтесь подводных камней. Часто люди связывают зависимости со своей библиотекой либо потому, что они нацелены на ужасную среду разработки (например, Windows), либо потому, что они поддерживают только старую (устаревшую) версию рассматриваемой библиотеки (зависимости). Основная ошибка заключается в том, что ваша связанная зависимость может конфликтовать с уже установленными версиями той же библиотеки / приложения (например, вы связали gtest, но у человека, пытающегося создать вашу библиотеку, уже установлена более новая (или старая) версия gtest, тогда эти двое могут столкнуться и вызвать у этого человека очень неприятную головную боль). Итак, как я уже сказал, делайте это на свой страх и риск, и я бы сказал только в крайнем случае. Просить людей установить несколько зависимостей до того, как вы сможете скомпилировать вашу библиотеку, - гораздо меньшее зло, чем попытка разрешить конфликты между вашими связанными зависимостями и существующими установками.
На мой взгляд, один файл cpp на класс (или небольшую связную группу классов и функций) более обычен и практичен. Однако определенно не компилируйте их все в один двоичный файл только для того, чтобы «все они могли работать вместе». Это действительно плохая идея. Обычно, когда дело доходит до кодирования, вы хотите разделить вещи настолько, насколько это разумно. В случае модульных тестов вы не хотите, чтобы один двоичный файл запускал все тесты, потому что это означает, что любое небольшое изменение, которое вы вносите во что-либо в своей библиотеке, вероятно, приведет к почти полной перекомпиляции этой программы модульного тестирования. , и это всего лишь минуты / часы, потерянные в ожидании перекомпиляции. Просто придерживайтесь простой схемы: 1 юнит = 1 программа юнит-тестирования. Затем,
Я бы предпочел такой макет:
Здесь следует отметить несколько моментов. Во-первых, подкаталоги вашего каталога src должны отражать подкаталоги вашего каталога include, это просто для того, чтобы вещи были интуитивно понятными (также постарайтесь, чтобы структура подкаталогов была достаточно плоской (неглубокой), потому что глубокая вложенность папок часто доставляет больше хлопот, чем что-либо другое). Во-вторых, каталог include - это просто каталог установки, его содержимое - это все заголовки, выбранные из каталога src.
В-третьих, система CMake предназначена для распределения по исходным подкаталогам, а не как один файл CMakeLists.txt на верхнем уровне. Благодаря этому вещи остаются локальными, и это хорошо (в духе разделения на независимые части). Если вы добавляете новый источник, новый заголовок или новую тестовую программу, все, что вам нужно, это отредактировать один небольшой и простой файл CMakeLists.txt в соответствующем подкаталоге, не затрагивая ничего другого. Это также позволяет легко реструктурировать каталоги (списки CMakeList являются локальными и содержатся в перемещаемых подкаталогах). CMakeLists верхнего уровня должен содержать большинство конфигураций верхнего уровня, таких как настройка каталогов назначения, пользовательские команды (или макросы) и поиск пакетов, установленных в системе. CMakeLists нижнего уровня должны содержать только простые списки заголовков, источников,
Основной ответ заключается в том, что CMake позволяет вам специально исключать определенные цели из «всех» (что и создается при вводе «make»), а также вы можете создавать определенные пакеты целей. Я не могу сделать здесь учебник по CMake, но это довольно просто выяснить самому. Однако в этом конкретном случае рекомендуемым решением, конечно же, является использование CTest, который представляет собой просто дополнительный набор команд, которые вы можете использовать в файлах CMakeLists для регистрации ряда целей (программ), которые помечены как единичные. тесты. Итак, CMake поместит все тесты в специальную категорию сборок, и это именно то, о чем вы просили, поэтому проблема решена.
Наличие каталога сборки вне исходного кода (сборка «вне исходного кода») - это действительно единственное разумное решение, которое в наши дни является стандартом де-факто. Так что определенно создайте отдельный каталог «build» вне исходного каталога, как рекомендуют специалисты CMake и как это делает каждый программист, которого я когда-либо встречал. Что касается каталога bin, ну, это соглашение, и, вероятно, будет хорошей идеей придерживаться его, как я сказал в начале этого поста.
Да. Это более чем возможно, это круто. В зависимости от того, насколько нарядно вы хотите выглядеть, есть несколько возможностей. CMake имеет модуль для Doxygen (т.е.
find_package(Doxygen)
), который позволяет вам регистрировать цели, которые будут запускать Doxygen для некоторых файлов. Если вы хотите делать более необычные вещи, например, обновлять номер версии в Doxyfile или автоматически вводить дату / дату / дату для исходных файлов и т. Д., Все это возможно с помощью небольшого количества CMake kung-fu. Как правило, для этого необходимо сохранить исходный Doxyfile (например, «Doxyfile.in», который я поместил в макет папки выше), в котором есть токены, которые необходимо найти и заменить командами синтаксического анализа CMake. В моем файле CMakeLists верхнего уровня вы найдете один такой кусок CMake kung-fu, который вместе с cmake-doxygen делает несколько причудливых вещей.источник
main.cpp
следует пойтиtrunk/bin
?Структурирование проекта
Я бы предпочел следующее:
Это означает, что у вас есть очень четко определенный набор файлов API для вашей библиотеки, а структура означает, что клиенты вашей библиотеки будут делать
а не менее явный
Мне нравится, что структура дерева / src соответствует структуре дерева / include, но на самом деле это личное предпочтение. Однако, если ваш проект расширяется и содержит подкаталоги внутри / include / project, обычно помогает сопоставление с подкаталогами внутри / src дерева.
Что касается тестов, я предпочитаю держать их «близко» к файлам, которые они тестируют, и если у вас все-таки остались подкаталоги в / src, это довольно простая парадигма, которой могут следовать другие, если они хотят найти тестовый код данного файла.
тестирование
Gtest действительно прост в использовании и довольно обширен с точки зрения своих возможностей. Его можно очень легко использовать вместе с gmock, чтобы расширить его возможности, но мой собственный опыт работы с gmock был менее благоприятным. Я вполне готов согласиться с тем, что это вполне может быть связано с моими собственными недостатками, но тесты gmock, как правило, труднее создавать и намного более хрупкие / трудные в обслуживании. Большой гвоздь в гробу gmock заключается в том, что он действительно плохо работает с умными указателями.
Это очень тривиальный и субъективный ответ на огромный вопрос (который, вероятно, действительно не относится к SO)
Я предпочитаю использовать
ExternalProject_Add
модуль CMake . Это избавляет вас от необходимости хранить исходный код gtest в вашем репозитории или устанавливать его где угодно. Он автоматически загружается и встраивается в ваше дерево сборки.См. Мой ответ о деталях здесь .
Хороший план.
Здание
Я поклонник CMake, но, как и в случае с вашими вопросами, связанными с тестами, SO, вероятно, не лучшее место, чтобы узнать мнение по такой субъективной проблеме.
Библиотека будет отображаться как целевая «ProjectLibrary», а набор тестов - как целевой «ProjectTest». Если указать библиотеку как зависимость от тестового exe, сборка тестового exe автоматически приведет к перестройке библиотеки, если она устарела.
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.Да, хотя у меня нет опыта в этом отношении. CMake предоставляет
FindDoxygen
для этого.источник
В дополнение к другим (отличным) ответам я собираюсь описать структуру, которую я использовал для относительно крупномасштабных проектов.
Я не собираюсь затрагивать подвопрос о Doxygen, так как я просто повторю то, что сказано в других ответах.
обоснование
Для модульности и ремонтопригодности проект организован как набор небольших модулей. Для ясности назовем их UnitX, где X = A, B, C, ... (но они могут иметь любое общее имя). Затем структура каталогов организована таким образом, чтобы отразить этот выбор, с возможностью группировки единиц, если необходимо.
Решение
Базовая структура каталога следующая (содержание модулей подробно описано ниже):
project/CMakeLists.txt
может содержать следующее:и
project/GroupA/CMakeLists.txt
:и
project/GroupB/CMakeLists.txt
:Теперь о структуре различных юнитов (возьмем, например, UnitD)
К различным компонентам:
.cpp
) и headers (.h
) находятся в одной папке. Это позволяет избежать дублирования иерархии каталогов и упрощает обслуживание. Для установки не проблема (особенно с CMake) просто отфильтровать файлы заголовков.UnitD
- позже разрешить включение файлов с расширением#include <UnitD/ClassA.h>
. Кроме того, при установке этого модуля вы можете просто скопировать структуру каталогов как есть. Обратите внимание, что вы также можете организовать свои исходные файлы в подкаталогах.README
файл, в котором кратко изложено, о чем идет речь, и указана полезная информация о нем.CMakeLists.txt
может просто содержать:lib/CMakeLists.txt
:Здесь обратите внимание, что нет необходимости сообщать CMake, что нам нужны каталоги включения для
UnitA
иUnitC
, поскольку это уже было указано при настройке этих модулей. Кроме того,PUBLIC
все зависящие от цели цели сообщат,UnitD
что они должны автоматически включатьUnitA
зависимость, тогда как тогдаUnitC
это не потребуется (PRIVATE
).test/CMakeLists.txt
(см. ниже, если вы хотите использовать для этого GTest):Использование GoogleTest
Для Google Test проще всего, если его источник находится где-то в вашем исходном каталоге, но вам не обязательно добавлять его туда самостоятельно. Я использую этот проект для его автоматической загрузки, и я заключил его в функцию, чтобы убедиться, что он загружается только один раз, даже если у нас есть несколько тестовых целей.
Эта функция CMake следующая:
а затем, когда я захочу использовать его в одной из моих тестовых целей, я добавлю следующие строки в
CMakeLists.txt
(это для примера вышеtest/CMakeLists.txt
):источник