C ++ Projektorganisation (mit gtest, cmake und doxygen)


123

Ich bin neu in der Programmierung im Allgemeinen und habe beschlossen, zunächst eine einfache Vektorklasse in C ++ zu erstellen. Ich möchte jedoch von Anfang an gute Gewohnheiten lernen, anstatt später zu versuchen, meinen Workflow zu ändern.

Ich habe derzeit nur zwei Dateien vector3.hppund vector3.cpp. Dieses Projekt wird langsam wachsen (was es viel mehr zu einer allgemeinen linearen Algebra-Bibliothek macht), wenn ich mit allem vertraut werde. Daher möchte ich ein "Standard" -Projektlayout verwenden, um das Leben später einfacher zu machen. Nachdem ich mich umgesehen habe, habe ich zwei Möglichkeiten gefunden, um hpp- und cpp-Dateien zu organisieren. Die erste ist:

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

und das zweite Wesen:

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

Welches würdest du empfehlen und warum?

Zweitens möchte ich das Google C ++ Testing Framework zum Testen meines Codes verwenden, da es ziemlich einfach zu verwenden scheint. Schlagen Sie vor, dies mit meinem Code zu bündeln, beispielsweise in einem inc/gtestoder einem contrib/gtestOrdner? Wenn Sie gebündelt sind, schlagen Sie vor, das fuse_gtest_files.pySkript zu verwenden, um die Anzahl oder die Dateien zu reduzieren, oder es unverändert zu lassen? Wenn nicht gebündelt, wie wird diese Abhängigkeit behandelt?

Wie sind diese beim Schreiben von Tests im Allgemeinen organisiert? Ich dachte, ich hätte eine CPP-Datei für jede Klasse ( test_vector3.cppzum Beispiel), aber alle in einer Binärdatei kompiliert, damit sie alle einfach zusammen ausgeführt werden können.

Da die gtest-Bibliothek im Allgemeinen mit cmake und make erstellt wird, dachte ich, dass es sinnvoll wäre, mein Projekt auch so zu erstellen? Wenn ich mich für das folgende Projektlayout entschieden habe:

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

Wie müsste das CMakeLists.txtaussehen, damit es entweder nur die Bibliothek oder die Bibliothek und die Tests erstellen kann? Außerdem habe ich einige Projekte gesehen, die ein buildund ein binVerzeichnis haben. Findet der Build im Build-Verzeichnis statt und werden die Binärdateien in das bin-Verzeichnis verschoben? Würden sich die Binärdateien für die Tests und die Bibliothek am selben Ort befinden? Oder wäre es sinnvoller, es wie folgt zu strukturieren:

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

Ich möchte auch doxygen verwenden, um meinen Code zu dokumentieren. Ist es möglich, dass dies automatisch mit cmake and make ausgeführt wird?

Entschuldigung für so viele Fragen, aber ich habe kein Buch über C ++ gefunden, das diese Art von Fragen zufriedenstellend beantwortet.


6
Gute Frage, aber ich denke nicht, dass sie gut zum Q & A-Format von Stack Overflow passt . Ich bin allerdings sehr an einer Antwort interessiert. +1 & fav
Luchian Grigore

1
Dies sind viele Fragen in riesigen. Möge es besser sein, es in mehrere kleinere Fragen aufzuteilen und Links zueinander zu setzen. Wie auch immer, um den letzten Teil zu beantworten: Mit CMake können Sie wählen, ob Sie innerhalb und außerhalb Ihres src-Verzeichnisses erstellen möchten (ich würde außerhalb empfehlen). Und ja, Sie können Sauerstoff mit CMake automatisch verwenden.
Mistapink

Antworten:


84

C ++ - Build-Systeme sind ein bisschen schwarz und je älter das Projekt ist, desto seltsamer sind die Dinge, die Sie finden können. Es ist also nicht verwunderlich, dass viele Fragen auftauchen. Ich werde versuchen, die Fragen einzeln durchzugehen und einige allgemeine Dinge zum Erstellen von C ++ - Bibliotheken zu erwähnen.

Trennen von Headern und CPP-Dateien in Verzeichnissen. Dies ist nur wichtig, wenn Sie eine Komponente erstellen, die im Gegensatz zu einer tatsächlichen Anwendung als Bibliothek verwendet werden soll. Ihre Header sind die Basis für Benutzer, um mit dem zu interagieren, was Sie anbieten, und müssen installiert werden. Dies bedeutet, dass sie sich in einem Unterverzeichnis befinden müssen (niemand möchte, dass viele Header auf der obersten Ebene landen /usr/include/), und dass Ihre Header in der Lage sein müssen, sich in ein solches Setup einzuschließen.

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

funktioniert gut, da Include-Pfade funktionieren und Sie das einfache Globbing für Installationsziele verwenden können.

Abhängigkeiten bündeln: Ich denke, dies hängt weitgehend von der Fähigkeit des Build-Systems ab, Abhängigkeiten zu lokalisieren und zu konfigurieren und wie abhängig Ihr Code von einer einzelnen Version ist. Dies hängt auch davon ab, wie leistungsfähig Ihre Benutzer sind und wie einfach die Abhängigkeit bei der Installation auf ihrer Plattform ist. CMake wird mit einem find_packageSkript für Google Test geliefert. Das macht die Sache viel einfacher. Ich würde nur bei Bedarf mit dem Bündeln gehen und es sonst vermeiden.

Erstellen: Vermeiden Sie In-Source-Builds. CMake macht das Erstellen von Quellen einfach und erleichtert das Leben erheblich.

Ich nehme an, Sie möchten CTest auch verwenden, um Tests für Ihr System auszuführen (es bietet auch integrierte Unterstützung für GTest). Eine wichtige Entscheidung für das Verzeichnislayout und die Testorganisation wird sein: Haben Sie am Ende Teilprojekte? In diesem Fall benötigen Sie beim Einrichten von CMakeLists weitere Arbeiten und sollten Ihre Unterprojekte in Unterverzeichnisse mit jeweils eigenen Dateien includeund srcDateien aufteilen . Vielleicht läuft und gibt sogar ihr eigener Sauerstoff aus (das Kombinieren mehrerer Sauerstoffprojekte ist möglich, aber nicht einfach oder hübsch).

Sie werden am Ende so etwas haben:

└── 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

wo

  • (1) Konfiguriert Abhängigkeiten, Plattformspezifikationen und Ausgabepfade
  • (2) Konfiguriert die Bibliothek, die Sie erstellen möchten
  • (3) konfiguriert die ausführbaren Testdateien und Testfälle

Wenn Sie Unterkomponenten haben, würde ich vorschlagen, eine weitere Hierarchie hinzuzufügen und den obigen Baum für jedes Unterprojekt zu verwenden. Dann wird es schwierig, weil Sie entscheiden müssen, ob Unterkomponenten ihre Abhängigkeiten suchen und konfigurieren oder ob Sie dies in der obersten Ebene tun. Dies sollte von Fall zu Fall entschieden werden.

Doxygen: Nachdem Sie den Konfigurationstanz von Doxygen durchlaufen haben, ist es trivial, CMake add_custom_commandzum Hinzufügen eines Dokumentziels zu verwenden.

So enden meine Projekte und ich habe einige sehr ähnliche Projekte gesehen, aber das ist natürlich kein Allheilmittel.

Nachtrag Irgendwann möchten Sie eine config.hpp Datei generieren , die eine Versionsdefinition und möglicherweise eine Definition für eine Versionskontroll-ID (einen Git-Hash oder eine SVN-Versionsnummer) enthält. CMake verfügt über Module, um das Auffinden dieser Informationen zu automatisieren und Dateien zu generieren. Sie können CMakes verwenden configure_file, um Variablen in einer Vorlagendatei durch Variablen zu ersetzen, die innerhalb von definiert sind CMakeLists.txt.

Wenn Sie Bibliotheken __declspecerstellen, benötigen Sie auch eine Exportdefinition , um den Unterschied zwischen Compilern richtig zu machen, z. B. bei MSVC und visibilityAttributen bei GCC / clang.


2
Gute Antwort, aber ich bin mir immer noch nicht sicher, warum Sie Ihre Header-Dateien in ein zusätzliches Unterverzeichnis mit Projektnamen einfügen müssen: "/prj/include/prj/foo.hpp", das mir überflüssig erscheint. Warum nicht einfach "/prj/include/foo.hpp"? Ich gehe davon aus, dass Sie die Möglichkeit haben, die Installationsverzeichnisse zur Installationszeit neu auszurichten, sodass Sie bei der Installation <INSTALL_DIR> /include/prj/foo.hpp erhalten, oder ist das unter CMake schwierig?
William Payne

6
@ William Das ist eigentlich schwer mit CPack zu machen. Wie würden Ihre Includes in Quelldateien aussehen? Wenn sie in einer installierten Version nur "header.hpp" sind, muss sich "/ usr / include / prj /" im Include-Pfad befinden und nicht nur "/ usr / include".
PMR

37

Als Starter gibt es einige herkömmliche Namen für Verzeichnisse, die Sie nicht ignorieren können. Diese basieren auf der langen Tradition des Unix-Dateisystems. Diese sind:

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

Es ist wahrscheinlich eine gute Idee, sich an dieses Grundlayout zu halten, zumindest auf oberster Ebene.

Beim Aufteilen der Header- und Quelldateien (cpp) sind beide Schemata ziemlich häufig. Ich ziehe es jedoch vor, sie zusammen zu halten. Bei alltäglichen Aufgaben ist es nur praktischer, die Dateien zusammen zu haben. Wenn sich der gesamte Code unter einem Ordner der obersten Ebene befindet, dh unter dem trunk/src/Ordner, können Sie feststellen, dass sich alle anderen Ordner (bin, lib, include, doc und möglicherweise ein Testordner) zusätzlich zu Das "Build" -Verzeichnis für einen Out-of-Source-Build sind alle Ordner, die nur Dateien enthalten, die im Build-Prozess generiert werden. Daher muss nur der src-Ordner gesichert oder viel besser unter einem Versionskontrollsystem / -server (wie Git oder SVN) aufbewahrt werden.

Und wenn es darum geht, Ihre Header-Dateien auf dem Zielsystem zu installieren (wenn Sie Ihre Bibliothek eventuell verteilen möchten), verfügt CMake über einen Befehl zum Installieren von Dateien (erstellt implizit ein "install" -Ziel, um "make install" auszuführen) Sie können damit alle Header in das /usr/include/Verzeichnis einfügen . Ich benutze nur das folgende cmake-Makro für diesen Zweck:

# 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)

Wo SRCROOTist eine cmake-Variable, die ich auf den Ordner src gesetzt habe, und wo ist eine cmake-Variable, die ich INCLUDEROOTüberall dort konfiguriere, wo Header benötigt werden. Natürlich gibt es viele andere Möglichkeiten, dies zu tun, und ich bin sicher, dass mein Weg nicht der beste ist. Der Punkt ist, dass es keinen Grund gibt, die Header und Quellen zu teilen, nur weil nur die Header auf dem Zielsystem installiert werden müssen, da es insbesondere mit CMake (oder CPack) sehr einfach ist, die Header auszuwählen und zu konfigurieren installiert werden, ohne sie in einem separaten Verzeichnis haben zu müssen. Und das habe ich in den meisten Bibliotheken gesehen.

Quote: Zweitens möchte ich das Google C ++ Testing Framework zum Unit-Testen meines Codes verwenden, da es ziemlich einfach zu verwenden scheint. Schlagen Sie vor, dies mit meinem Code zu bündeln, beispielsweise in einem Ordner "inc / gtest" oder "contrib / gtest"? Wenn Sie gebündelt sind, schlagen Sie vor, das Skript fuse_gtest_files.py zu verwenden, um die Anzahl oder die Dateien zu reduzieren, oder es unverändert zu lassen? Wenn nicht gebündelt, wie wird diese Abhängigkeit behandelt?

Bündeln Sie keine Abhängigkeiten mit Ihrer Bibliothek. Dies ist im Allgemeinen eine ziemlich schreckliche Idee, und ich hasse es immer, wenn ich beim Versuch, eine Bibliothek aufzubauen, die das tut, nicht weiterkomme. Es sollte Ihr letzter Ausweg sein und sich vor den Fallstricken hüten. Häufig bündeln Benutzer Abhängigkeiten mit ihrer Bibliothek, entweder weil sie auf eine schreckliche Entwicklungsumgebung (z. B. Windows) abzielen oder weil sie nur eine alte (veraltete) Version der betreffenden Bibliothek (Abhängigkeit) unterstützen. Die größte Gefahr besteht darin, dass Ihre gebündelte Abhängigkeit möglicherweise mit bereits installierten Versionen derselben Bibliothek / Anwendung kollidiert (z. B. Sie haben gtest gebündelt, aber die Person, die versucht, Ihre Bibliothek zu erstellen, hat bereits eine neuere (oder ältere) Version von gtest installiert Die beiden könnten zusammenstoßen und dieser Person sehr schlimme Kopfschmerzen bereiten. Also, wie gesagt, tun Sie es auf eigenes Risiko, und ich würde nur als letzten Ausweg sagen. Das Auffordern der Benutzer, einige Abhängigkeiten zu installieren, bevor Sie Ihre Bibliothek kompilieren können, ist weitaus weniger schlimm als der Versuch, Konflikte zwischen Ihren gebündelten Abhängigkeiten und vorhandenen Installationen zu lösen.

Quote: Wie sind diese beim Schreiben von Tests allgemein organisiert? Ich dachte daran, eine CPP-Datei für jede Klasse zu haben (z. B. test_vector3.cpp), aber alle in einer Binärdatei kompiliert, damit sie alle problemlos zusammen ausgeführt werden können.

Eine CPP-Datei pro Klasse (oder eine kleine zusammenhängende Gruppe von Klassen und Funktionen) ist meiner Meinung nach üblicher und praktischer. Kompilieren Sie sie jedoch auf keinen Fall alle zu einer Binärdatei, nur damit "sie alle zusammen ausgeführt werden können". Das ist eine wirklich schlechte Idee. Wenn es um das Codieren geht, möchten Sie die Dinge im Allgemeinen so weit aufteilen, wie es vernünftig ist. Bei Komponententests möchten Sie nicht, dass eine Binärdatei alle Tests ausführt, da dies bedeutet, dass jede kleine Änderung, die Sie an irgendetwas in Ihrer Bibliothek vornehmen, wahrscheinlich zu einer nahezu vollständigen Neukompilierung dieses Komponententestprogramms führt und das sind nur Minuten / Stunden, die auf das erneute Kompilieren warten. Halten Sie sich einfach an ein einfaches Schema: 1 Einheit = 1 Einheitentestprogramm. Dann,

Quote: Da die gtest-Bibliothek in der Regel mit cmake und make erstellt wird, dachte ich, dass es sinnvoll wäre, mein Projekt auch so zu erstellen? Wenn ich mich für das folgende Projektlayout entschieden habe:

Ich würde eher dieses Layout vorschlagen:

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

Ein paar Dinge, die hier zu beachten sind. Erstens sollten die Unterverzeichnisse Ihres src-Verzeichnisses die Unterverzeichnisse Ihres include-Verzeichnisses widerspiegeln. Dies dient nur dazu, die Dinge intuitiv zu halten (versuchen Sie auch, Ihre Unterverzeichnisstruktur relativ flach (flach) zu halten, da Ordner tief verschachtelt sind ist oft mehr ein Ärger als alles andere). Zweitens ist das "include" -Verzeichnis nur ein Installationsverzeichnis. Sein Inhalt entspricht genau den Headern, die aus dem src-Verzeichnis ausgewählt wurden.

Drittens soll das CMake-System über die Quell-Unterverzeichnisse verteilt werden, nicht als eine CMakeLists.txt-Datei auf der obersten Ebene. Dies hält die Dinge lokal, und das ist gut so (im Geiste, die Dinge in unabhängige Teile aufzuteilen). Wenn Sie eine neue Quelle, einen neuen Header oder ein neues Testprogramm hinzufügen, müssen Sie lediglich eine kleine und einfache CMakeLists.txt-Datei in dem betreffenden Unterverzeichnis bearbeiten, ohne dass dies Auswirkungen hat. Auf diese Weise können Sie die Verzeichnisse auch problemlos umstrukturieren (CMakeLists sind lokal und in den zu verschiebenden Unterverzeichnissen enthalten). Die CMakeLists der obersten Ebene sollten die meisten Konfigurationen der obersten Ebene enthalten, z. B. das Einrichten von Zielverzeichnissen, benutzerdefinierten Befehlen (oder Makros) und das Auffinden von auf dem System installierten Paketen. Die untergeordneten CMakeLists sollten nur einfache Listen von Headern, Quellen,

Quote: Wie müsste die CMakeLists.txt aussehen, damit sie entweder nur die Bibliothek oder die Bibliothek und die Tests erstellen kann?

Die grundlegende Antwort lautet, dass Sie mit CMake bestimmte Ziele gezielt von "all" ausschließen können (was bei der Eingabe von "make" erstellt wird) und bestimmte Zielbündel erstellen können. Ich kann hier kein CMake-Tutorial machen, aber es ist ziemlich einfach, es selbst herauszufinden. In diesem speziellen Fall besteht die empfohlene Lösung natürlich darin, CTest zu verwenden. Dies ist nur ein zusätzlicher Befehlssatz, den Sie in den CMakeLists-Dateien verwenden können, um eine Reihe von Zielen (Programmen) zu registrieren, die als unit- gekennzeichnet sind. Tests. CMake wird also alle Tests in eine spezielle Kategorie von Builds einordnen, und genau darum haben Sie gebeten, um das Problem zu lösen.

Quote: Ich habe auch einige Projekte gesehen, die eine Build-Anzeige und ein Bin-Verzeichnis haben. Findet der Build im Build-Verzeichnis statt und werden die Binärdateien in das bin-Verzeichnis verschoben? Würden sich die Binärdateien für die Tests und die Bibliothek am selben Ort befinden? Oder wäre es sinnvoller, es wie folgt zu strukturieren:

Ein Build-Verzeichnis außerhalb der Quelle zu haben ("Out-of-Source" Build) ist wirklich das einzig Vernünftige, es ist heutzutage der De-facto-Standard. Haben Sie also definitiv ein separates "Build" -Verzeichnis außerhalb des Quellverzeichnisses, so wie es die CMake-Leute empfehlen und wie es jeder Programmierer tut, den ich jemals getroffen habe. Was das bin-Verzeichnis betrifft, so ist dies eine Konvention, und es ist wahrscheinlich eine gute Idee, sich daran zu halten, wie ich am Anfang dieses Beitrags sagte.

Quote: Ich möchte auch doxygen verwenden, um meinen Code zu dokumentieren. Ist es möglich, dass dies automatisch mit cmake and make ausgeführt wird?

Ja. Es ist mehr als möglich, es ist großartig. Je nachdem, wie ausgefallen Sie werden möchten, gibt es mehrere Möglichkeiten. CMake verfügt über ein Modul für Doxygen (dh find_package(Doxygen)), mit dem Sie Ziele registrieren können, auf denen Doxygen für einige Dateien ausgeführt wird. Wenn Sie ausgefallenere Dinge tun möchten, z. B. die Versionsnummer in der Doxy-Datei aktualisieren oder automatisch Datums- / Autorenstempel für Quelldateien eingeben usw., ist dies alles mit etwas CMake-Kung-Fu möglich. Im Allgemeinen bedeutet dies, dass Sie eine Quell-Doxy-Datei (z. B. die "Doxy-Datei.in", die ich in das obige Ordnerlayout eingefügt habe) behalten, deren Token gefunden und durch die Analysebefehle von CMake ersetzt werden müssen. In meiner CMakeLists-Datei der obersten Ebene finden Sie ein solches Stück CMake-Kung-Fu, das ein paar ausgefallene Dinge mit cmake-doxygen zusammen macht.


Also main.cppsollte gehen zu trunk/bin?
Ugnius Malūkas

17

Strukturierung des Projekts

Ich würde im Allgemeinen Folgendes bevorzugen:

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

Dies bedeutet, dass Sie einen sehr klar definierten Satz von API-Dateien für Ihre Bibliothek haben, und die Struktur bedeutet, dass Clients Ihrer Bibliothek dies tun würden

#include "project/vector3.hpp"

eher als die weniger explizite

#include "vector3.hpp"


Ich mag es, wenn die Struktur des / src-Baums mit der des / include-Baums übereinstimmt, aber das ist wirklich eine persönliche Präferenz. Wenn Ihr Projekt jedoch erweitert wird, um Unterverzeichnisse in / include / project zu enthalten, ist es im Allgemeinen hilfreich, die Verzeichnisse im / src-Baum abzugleichen.

Für die Tests bevorzuge ich es, sie "nah" an den Dateien zu halten, die sie testen, und wenn Sie Unterverzeichnisse in / src haben, ist es für andere ein ziemlich einfaches Paradigma, dem zu folgen, wenn sie den Testcode einer bestimmten Datei finden möchten.


Testen

Zweitens möchte ich das Google C ++ Testing Framework zum Testen meines Codes verwenden, da es ziemlich einfach zu verwenden scheint.

Gtest ist in der Tat einfach zu bedienen und in Bezug auf seine Fähigkeiten ziemlich umfassend. Es kann sehr einfach zusammen mit gmock verwendet werden, um seine Fähigkeiten zu erweitern, aber meine eigenen Erfahrungen mit gmock waren weniger günstig. Ich bin durchaus bereit zu akzeptieren, dass dies möglicherweise auf meine eigenen Mängel zurückzuführen ist, aber Gmock-Tests sind in der Regel schwieriger zu erstellen und viel fragiler / schwieriger zu warten. Ein großer Nagel im Gmock-Sarg ist, dass er mit intelligenten Zeigern wirklich nicht gut spielt.

Dies ist eine sehr triviale und subjektive Antwort auf eine große Frage (die wahrscheinlich nicht wirklich zu SO gehört).

Schlagen Sie vor, dies mit meinem Code zu bündeln, beispielsweise in einem Ordner "inc / gtest" oder "contrib / gtest"? Wenn Sie gebündelt sind, schlagen Sie vor, das Skript fuse_gtest_files.py zu verwenden, um die Anzahl oder die Dateien zu reduzieren, oder es unverändert zu lassen? Wenn nicht gebündelt, wie wird diese Abhängigkeit behandelt?

Ich bevorzuge die Verwendung des CMake- ExternalProject_AddModuls. Auf diese Weise müssen Sie den gtest-Quellcode nicht in Ihrem Repository behalten oder irgendwo installieren. Es wird automatisch heruntergeladen und in Ihren Build-Baum integriert.

Siehe meine Antwort zu den Einzelheiten hier .

Wie sind diese beim Schreiben von Tests im Allgemeinen organisiert? Ich dachte daran, eine cpp-Datei für jede Klasse zu haben (zum Beispiel test_vector3.cpp), aber alle in einer Binärdatei kompiliert, damit sie alle zusammen ausgeführt werden können?

Guter Plan.


Gebäude

Ich bin ein Fan von CMake, aber wie bei Ihren testbezogenen Fragen ist SO wahrscheinlich nicht der beste Ort, um Meinungen zu einem solchen subjektiven Thema einzuholen.

Wie müsste die CMakeLists.txt aussehen, damit sie entweder nur die Bibliothek oder die Bibliothek und die Tests erstellen kann?

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

Die Bibliothek wird als Ziel "ProjectLibrary" und die Testsuite als Ziel "ProjectTest" angezeigt. Wenn Sie die Bibliothek als Abhängigkeit von der Test-Exe angeben, wird beim Erstellen der Test-Exe die Bibliothek automatisch neu erstellt, wenn sie veraltet ist.

Außerdem habe ich einige Projekte gesehen, die eine Build-Anzeige und ein Bin-Verzeichnis haben. Findet der Build im Build-Verzeichnis statt und werden die Binärdateien in das bin-Verzeichnis verschoben? Würden sich die Binärdateien für die Tests und die Bibliothek am selben Ort befinden?

CMake empfiehlt Builds außerhalb der Quelle, dh Sie erstellen ein eigenes Build-Verzeichnis außerhalb des Projekts und führen CMake von dort aus aus. Dies vermeidet, dass Ihr Quellbaum mit Build-Dateien "verschmutzt" wird, und ist äußerst wünschenswert, wenn Sie einen VCS verwenden.

Sie können angeben, dass die Binärdateien nach dem Erstellen in ein anderes Verzeichnis verschoben oder kopiert werden oder dass sie standardmäßig in einem anderen Verzeichnis erstellt werden, dies ist jedoch im Allgemeinen nicht erforderlich. CMake bietet umfassende Möglichkeiten, um Ihr Projekt auf Wunsch zu installieren oder anderen CMake-Projekten das "Finden" der relevanten Dateien Ihres Projekts zu erleichtern.

In Bezug auf CMakes eigene Unterstützung beim Finden und Ausführen von gtest-Tests wäre dies weitgehend unangemessen, wenn Sie gtest als Teil Ihres Projekts erstellen. Das FindGtestModul ist wirklich für den Fall konzipiert, dass gtest separat außerhalb Ihres Projekts erstellt wurde.

CMake bietet ein eigenes Test-Framework (CTest), und im Idealfall wird jeder gtest-Fall als CTest-Fall hinzugefügt.

Das GTEST_ADD_TESTSMakro , das bereitgestellt wird FindGtest, um das einfache Hinzufügen von gtest-Fällen als einzelne ctest-Fälle zu ermöglichen, fehlt jedoch etwas, da es für andere gtest-Makros als TESTund nicht funktioniert TEST_F. Value- oder Typ-parametrisiert Tests mit TEST_P,TYPED_TEST_P etc. werden überhaupt nicht behandelt.

Das Problem hat keine einfache Lösung, die ich kenne. Der robusteste Weg, um eine Liste von gtest-Fällen zu erhalten, besteht darin, die Test-Exe mit dem Flag auszuführen --gtest_list_tests. Dies ist jedoch nur möglich, wenn die Exe erstellt wurde, sodass CMake dies nicht nutzen kann. Was Ihnen zwei Möglichkeiten lässt; CMake muss versuchen, C ++ - Code zu analysieren, um die Namen der Tests abzuleiten (im Extremfall nicht trivial, wenn Sie alle gtest-Makros, auskommentierten Tests, deaktivierten Tests berücksichtigen möchten), oder Testfälle werden manuell zum hinzugefügt CMakeLists.txt-Datei.

Ich möchte auch doxygen verwenden, um meinen Code zu dokumentieren. Ist es möglich, dass dies automatisch mit cmake and make ausgeführt wird?

Ja - obwohl ich keine Erfahrung auf diesem Gebiet habe. CMake sieht FindDoxygendies vor.


6

Zusätzlich zu den anderen (ausgezeichneten) Antworten werde ich eine Struktur beschreiben, die ich für relativ große Projekte verwendet habe.
Ich werde die Unterfrage zu Doxygen nicht ansprechen, da ich nur wiederholen würde, was in den anderen Antworten gesagt wird.


Begründung

Aus Gründen der Modularität und Wartbarkeit ist das Projekt in kleinen Einheiten organisiert. Nennen wir sie der Klarheit halber UnitX mit X = A, B, C, ... (sie können jedoch einen beliebigen allgemeinen Namen haben). Die Verzeichnisstruktur wird dann so organisiert, dass sie diese Auswahl widerspiegelt, und bei Bedarf können Einheiten gruppiert werden.

Lösung

Das grundlegende Verzeichnislayout ist das folgende (der Inhalt der Einheiten wird später detailliert beschrieben):

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

project/CMakeLists.txt könnte Folgendes enthalten:

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)

und project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

und project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Nun zur Struktur der verschiedenen Einheiten (nehmen wir als Beispiel 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]

Zu den verschiedenen Komponenten:

  • Ich mag es, source ( .cpp) und headers ( .h) im selben Ordner zu haben. Dies vermeidet eine doppelte Verzeichnishierarchie und erleichtert die Wartung. Für die Installation ist es kein Problem (insbesondere bei CMake), nur die Header-Dateien zu filtern.
  • Die Rolle des Verzeichnisses UnitDbesteht darin, später das Einschließen von Dateien mit zuzulassen#include <UnitD/ClassA.h> . Bei der Installation dieses Geräts können Sie die Verzeichnisstruktur einfach so kopieren, wie sie ist. Beachten Sie, dass Sie Ihre Quelldateien auch in Unterverzeichnissen organisieren können.
  • Ich mag eine READMEDatei, um zusammenzufassen, worum es bei dem Gerät geht, und um nützliche Informationen darüber anzugeben.
  • CMakeLists.txt könnte einfach enthalten:

    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
                          )

    Beachten Sie hier, dass es nicht erforderlich ist, CMake mitzuteilen, dass wir die Include-Verzeichnisse für UnitAund möchten UnitC, da dies bereits bei der Konfiguration dieser Einheiten angegeben wurde. Außerdem PUBLICwird allen Zielen, die davon abhängen, mitgeteilt, UnitDdass sie die UnitAAbhängigkeit automatisch einschließen sollen , obwohl dies UnitCdann nicht erforderlich ist ( PRIVATE).

  • test/CMakeLists.txt (siehe weiter unten, wenn Sie GTest dafür verwenden möchten):

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

Verwenden von GoogleTest

Für Google Test ist es am einfachsten, wenn sich die Quelle irgendwo in Ihrem Quellverzeichnis befindet, Sie sie dort jedoch nicht selbst hinzufügen müssen. Ich habe dieses Projekt benutzt , um es automatisch herunterzuladen, und ich habe seine Verwendung in eine Funktion eingeschlossen, um sicherzustellen, dass es nur einmal heruntergeladen wird, obwohl wir mehrere Testziele haben.

Diese CMake-Funktion ist die folgende:

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()

und wenn ich es dann in einem meiner Testziele verwenden möchte, füge ich die folgenden Zeilen hinzu CMakeLists.txt(dies ist für das obige Beispiel test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)

Netter "Hack", den du dort mit Gtest und cmake gemacht hast! Nützlich! :)
Tanasis
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.