Organizacja projektów C ++ (z gtest, cmake i doxygen)

123

Ogólnie jestem nowy w programowaniu, więc zdecydowałem, że zacznę od stworzenia prostej klasy wektorowej w C ++. Jednak od samego początku chciałbym wypracować dobre nawyki, zamiast próbować później modyfikować mój przepływ pracy.

Obecnie mam tylko dwa pliki vector3.hppi vector3.cpp. Ten projekt będzie się powoli rozwijał (czyniąc go bardziej ogólną biblioteką algebry liniowej), gdy będę się ze wszystkim lepiej zaznajamiał, więc chciałbym przyjąć „standardowy” układ projektu, aby później ułatwić życie. Więc po rozejrzeniu się znalazłem dwa sposoby organizowania plików hpp i cpp, pierwszy to:

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

a druga istota:

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

Które byś polecił i dlaczego?

Po drugie, chciałbym użyć Google C ++ Testing Framework do testowania jednostkowego mojego kodu, ponieważ wydaje się on dość łatwy w użyciu. Czy sugerujesz dołączenie tego do mojego kodu, na przykład w folderze inc/gtestlub contrib/gtest? Jeśli jest dołączony, czy sugerujesz użycie fuse_gtest_files.pyskryptu w celu zmniejszenia liczby lub plików, czy też pozostawienie go tak, jak jest? Jeśli nie w pakiecie, jak jest obsługiwana ta zależność?

Jeśli chodzi o pisanie testów, jak są one ogólnie zorganizowane? Myślałem o jednym pliku cpp dla każdej klasy ( test_vector3.cppna przykład), ale wszystkie skompilowane do jednego pliku binarnego, aby można je było łatwo uruchomić razem?

Ponieważ biblioteka gtest jest generalnie budowana przy użyciu cmake i make, pomyślałem, że miałoby sens, aby mój projekt był również budowany w ten sposób? Gdybym zdecydował się na następujący układ projektu:

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

Jak musiałby CMakeLists.txtwyglądać, aby mógł budować tylko bibliotekę lub bibliotekę i testy? Widziałem też sporo projektów, które mają buildi do binkatalogu. Czy kompilacja odbywa się w katalogu kompilacji, a następnie pliki binarne są przenoszone do katalogu bin? Czy pliki binarne dla testów i biblioteki będą znajdować się w tym samym miejscu? A może bardziej sensowne byłoby sformułowanie tego w następujący sposób:

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

Chciałbym również użyć doxygen do udokumentowania mojego kodu. Czy możliwe jest automatyczne uruchamianie tego z cmake i make?

Przepraszam za tak wiele pytań, ale nie znalazłem książki o C ++, która w zadowalający sposób odpowiada na tego typu pytania.

rozzy
źródło
6
Świetne pytanie, ale nie sądzę, aby pasowało do formatu pytań i odpowiedzi Stack Overflow . Jestem jednak bardzo zainteresowany odpowiedzią. +1 & fav
Luchian Grigore
1
Jest wiele pytań dotyczących ogromnych. Oby lepiej podzielić to na kilka mniejszych pytań i umieścić linki do siebie. W każdym razie odpowiadając na ostatnią część: z CMake możesz wybrać budowanie wewnątrz i na zewnątrz katalogu src (polecam na zewnątrz). I tak, możesz automatycznie używać doxygen z CMake.
mistapink

Odpowiedzi:

84

Systemy kompilacji C ++ są trochę czarną sztuką i im starszy projekt, tym bardziej dziwne rzeczy można znaleźć, więc nie jest zaskakujące, że pojawia się wiele pytań. Spróbuję przejść przez pytania jeden po drugim i wspomnieć o kilku ogólnych rzeczach dotyczących budowania bibliotek C ++.

Oddzielanie nagłówków i plików cpp w katalogach. Jest to niezbędne tylko wtedy, gdy tworzysz komponent, który ma być używany jako biblioteka, a nie rzeczywista aplikacja. Twoje nagłówki są podstawą interakcji użytkowników z Twoją ofertą i muszą zostać zainstalowane. Oznacza to, że muszą znajdować się w podkatalogu (nikt nie chce, aby wiele nagłówków kończyło się na najwyższym poziomie /usr/include/), a Twoje nagłówki muszą być w stanie uwzględnić się w takiej konfiguracji.

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

działa dobrze, ponieważ ścieżki dołączania działają i możesz użyć łatwego globowania dla celów instalacji.

Pakowanie zależności: myślę, że w dużej mierze zależy to od zdolności systemu kompilacji do lokalizowania i konfigurowania zależności oraz od tego, jak zależny jest twój kod od pojedynczej wersji. Zależy to również od tego, jak zdolni są Twoi użytkownicy i jak łatwa jest instalacja zależności na ich platformie. CMake zawiera find_packageskrypt do Google Test. To znacznie ułatwia sprawę. Pójdę z pakietem tylko wtedy, gdy jest to konieczne i unikam tego w przeciwnym razie.

Jak budować: unikaj kompilacji w źródle. CMake ułatwia tworzenie kodu źródłowego i znacznie ułatwia życie.

Przypuszczam, że chcesz również użyć CTest do uruchamiania testów dla swojego systemu (jest również wyposażony we wbudowaną obsługę GTest). Ważną decyzją dotyczącą układu katalogu i organizacji testów będzie: czy otrzymujesz podprojekty? Jeśli tak, potrzebujesz trochę pracy podczas konfigurowania CMakeLists i powinieneś podzielić podprojekty na podkatalogi, każdy z własnymi includei srcplikami. Może nawet ich własne przebiegi i wyjścia doxygen (łączenie wielu projektów doxygen jest możliwe, ale nie jest łatwe ani ładne).

Skończysz z czymś takim:

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

gdzie

  • (1) konfiguruje zależności, specyfikę platformy i ścieżki wyjściowe
  • (2) konfiguruje bibliotekę, którą zamierzasz zbudować
  • (3) konfiguruje pliki wykonywalne testów i przypadki testowe

Jeśli masz podkomponenty, sugerowałbym dodanie innej hierarchii i użycie powyższego drzewa dla każdego podprojektu. Wtedy sprawy stają się trudne, ponieważ musisz zdecydować, czy podkomponenty przeszukują i konfigurują ich zależności, czy też robisz to na najwyższym poziomie. Decyzja o tym powinna być podejmowana indywidualnie dla każdego przypadku.

Doxygen: Po przejściu przez taniec konfiguracji doxygen, użycie CMake add_custom_commanddo dodania celu dokumentu jest trywialne .

Tak kończą się moje projekty i widziałem kilka bardzo podobnych projektów, ale oczywiście to nie jest lekarstwo na wszystko.

Dodatek W pewnym momencie będziesz chciał wygenerować config.hpp plik, który zawiera definicję wersji i być może definicję jakiegoś identyfikatora kontroli wersji (skrót Git lub numer wersji SVN). CMake ma moduły do ​​automatyzacji wyszukiwania tych informacji i generowania plików. Możesz użyć CMake, configure_fileaby zastąpić zmienne w pliku szablonu zmiennymi zdefiniowanymi wewnątrz CMakeLists.txt.

Jeśli budujesz biblioteki, będziesz również potrzebować definicji eksportu, aby uzyskać właściwą różnicę między kompilatorami, np. __declspecNa MSVC i visibilityatrybutach na GCC / clang.

pmr
źródło
2
Dobra odpowiedź, ale nadal nie wiem, dlaczego musisz umieścić swoje pliki nagłówkowe w dodatkowym podkatalogu o nazwie projektu: „/prj/include/prj/foo.hpp”, który wydaje mi się zbędny. Dlaczego nie po prostu „/prj/include/foo.hpp”? Zakładam, że będziesz miał okazję ponownie połączyć katalogi instalacyjne w czasie instalacji, więc podczas instalacji otrzymasz <INSTALL_DIR> /include/prj/foo.hpp, czy jest to trudne w przypadku CMake?
William Payne,
6
@William To jest właściwie trudne do zrobienia z CPack. Jak wyglądałyby twoje dołączenia w plikach źródłowych? Jeśli są to po prostu „header.hpp” w zainstalowanej wersji, „/ usr / include / prj /” musi znajdować się w ścieżce dołączania, a nie tylko „/ usr / include”.
pmr
37

Na początek istnieje kilka konwencjonalnych nazw katalogów, których nie można zignorować. Są one oparte na długiej tradycji z systemem plików Unix. To są:

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

Prawdopodobnie dobrze jest trzymać się tego podstawowego układu, przynajmniej na najwyższym poziomie.

Jeśli chodzi o dzielenie plików nagłówkowych i plików źródłowych (cpp), oba schematy są dość powszechne. Jednak wolę trzymać je razem, po prostu bardziej praktyczne jest trzymanie plików razem w codziennych zadaniach. Ponadto, gdy cały kod znajduje się w jednym folderze najwyższego poziomu, tj. trunk/src/Folderze, można zauważyć, że wszystkie inne foldery (bin, lib, include, doc i może jakiś folder testowy) na najwyższym poziomie, oprócz katalog „build” dla kompilacji spoza źródła to wszystkie foldery, które zawierają tylko pliki generowane w procesie budowania. Dlatego tylko folder src musi być zarchiwizowany lub, o wiele lepiej, przechowywany w systemie kontroli wersji / serwerze (takim jak Git lub SVN).

A jeśli chodzi o instalowanie plików nagłówkowych w systemie docelowym (jeśli chcesz ostatecznie dystrybuować swoją bibliotekę), cóż, CMake ma polecenie do instalowania plików (niejawnie tworzy cel „instalacji”, aby wykonać „instalację”), które możesz użyć, aby umieścić wszystkie nagłówki w /usr/include/katalogu. W tym celu używam tylko następującego makra 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)

Gdzie SRCROOTjest zmienna cmake, którą ustawiłem na folder src, i INCLUDEROOTjest to zmienna cmake, którą konfiguruję, aby wszędzie tam, gdzie mają iść nagłówki. Oczywiście jest na to wiele innych sposobów i jestem pewien, że mój sposób nie jest najlepszy. Chodzi o to, że nie ma powodu, aby dzielić nagłówki i źródła tylko dlatego, że tylko nagłówki muszą być zainstalowane w systemie docelowym, ponieważ jest bardzo łatwe, szczególnie z CMake (lub CPack), aby wybrać i skonfigurować nagłówki do być instalowane bez konieczności umieszczania ich w oddzielnym katalogu. I to właśnie widziałem w większości bibliotek.

Cytat: Po drugie, chciałbym użyć Google C ++ Testing Framework do testów jednostkowych mojego kodu, ponieważ wydaje się on dość łatwy w użyciu. Czy sugerujesz dołączenie tego do mojego kodu, na przykład w folderze „inc / gtest” lub „contrib / gtest”? Jeśli jest dołączony, czy sugerujesz użycie skryptu fuse_gtest_files.py w celu zmniejszenia liczby lub plików, czy też pozostawienie go tak, jak jest? Jeśli nie w pakiecie, jak jest obsługiwana ta zależność?

Nie łącz zależności ze swoją biblioteką. Generalnie jest to dość okropny pomysł i zawsze go nienawidzę, kiedy utknąłem, próbując zbudować bibliotekę, która to zrobiła. To powinna być twoja ostatnia deska ratunku i uważaj na pułapki. Często ludzie łączą zależności ze swoją biblioteką albo dlatego, że celują w okropne środowisko programistyczne (np. Windows), albo dlatego, że obsługują tylko starą (przestarzałą) wersję danej biblioteki (zależność). Główną pułapką jest to, że Twoja powiązana zależność może kolidować z już zainstalowanymi wersjami tej samej biblioteki / aplikacji (np. Spakowałeś gtest, ale osoba próbująca zbudować bibliotekę ma już zainstalowaną nowszą (lub starszą) wersję gtest, a następnie mogą się zderzyć i sprawić tej osobie bardzo okropny ból głowy). Więc, jak powiedziałem, zrób to na własne ryzyko, i powiedziałbym tylko w ostateczności. Poproszenie ludzi o zainstalowanie kilku zależności, zanim będzie można skompilować bibliotekę, jest znacznie mniejszym złem niż próba rozwiązania konfliktów między powiązanymi zależnościami a istniejącymi instalacjami.

Cytat: Jeśli chodzi o pisanie testów, jak są one ogólnie zorganizowane? Myślałem o jednym pliku cpp dla każdej klasy (na przykład test_vector3.cpp), ale wszystkie skompilowane do jednego pliku binarnego, aby można je było łatwo uruchomić razem?

Jeden plik cpp na klasę (lub małą spójną grupę klas i funkcji) jest moim zdaniem bardziej zwyczajny i praktyczny. Jednak zdecydowanie nie kompiluj ich wszystkich w jeden plik binarny tylko po to, aby „wszystkie mogły działać razem”. To naprawdę zły pomysł. Ogólnie rzecz biorąc, jeśli chodzi o kodowanie, chcesz podzielić rzeczy na tyle, na ile jest to uzasadnione. W przypadku testów jednostkowych nie chcesz, aby jeden plik binarny uruchamiał wszystkie testy, ponieważ oznacza to, że każda niewielka zmiana, którą wprowadzisz do czegokolwiek w swojej bibliotece, może spowodować prawie całkowitą rekompilację tego programu testów jednostkowych , a to tylko minuty / godziny stracone w oczekiwaniu na rekompilację. Po prostu trzymaj się prostego schematu: 1 jednostka = 1 program testów jednostkowych. Następnie,

Cytat: Ponieważ biblioteka gtest jest generalnie budowana przy użyciu cmake i make, pomyślałem, że miałoby sens, aby mój projekt był również budowany w ten sposób? Gdybym zdecydował się na następujący układ projektu:

Raczej zasugerowałbym taki układ:

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

Kilka rzeczy, na które należy zwrócić uwagę. Po pierwsze, podkatalogi twojego katalogu src powinny odzwierciedlać podkatalogi twojego katalogu include, to tylko po to, aby zachować intuicyjność (także staraj się utrzymać strukturę podkatalogów w miarę płaską (płytką), ponieważ głębokie zagnieżdżanie folderów jest często bardziej kłopotliwa niż cokolwiek innego). Po drugie, katalog „include” jest po prostu katalogiem instalacyjnym, a jego zawartością są po prostu wszystkie nagłówki, które są pobierane z katalogu src.

Po trzecie, system CMake ma być dystrybuowany w podkatalogach źródłowych, a nie jako jeden plik CMakeLists.txt na najwyższym poziomie. Dzięki temu rzeczy są lokalne i to jest dobre (w duchu dzielenia rzeczy na niezależne części). Jeśli dodasz nowe źródło, nowy nagłówek lub nowy program testowy, wszystko, czego potrzebujesz, to edytować jeden mały i prosty plik CMakeLists.txt w danym podkatalogu, bez wpływu na cokolwiek innego. Pozwala to również na łatwą restrukturyzację katalogów (listy CMakeLists są lokalne i zawarte w przenoszonych podkatalogach). CMakeLists najwyższego poziomu powinny zawierać większość konfiguracji najwyższego poziomu, takich jak konfigurowanie katalogów docelowych, niestandardowe polecenia (lub makra) i znajdowanie pakietów zainstalowanych w systemie. Niższy poziom CMakeLists powinien zawierać tylko proste listy nagłówków, źródeł,

Cytat: Jak musiałby wyglądać plik CMakeLists.txt, aby mógł zbudować tylko bibliotekę lub bibliotekę i testy?

Podstawowa odpowiedź jest taka, że ​​CMake pozwala konkretnie wykluczyć określone cele ze „wszystkich” (co jest budowane po wpisaniu „make”), a także można tworzyć określone zestawy celów. Nie mogę tutaj zrobić samouczka dotyczącego CMake, ale dość łatwo jest się przekonać samemu. Jednak w tym konkretnym przypadku zalecanym rozwiązaniem jest oczywiście użycie CTest, który jest tylko dodatkowym zestawem poleceń, których można użyć w plikach CMakeLists do zarejestrowania wielu celów (programów) oznaczonych jako jednostka- testy. Tak więc CMake umieści wszystkie testy w specjalnej kategorii kompilacji i właśnie o to prosiłeś, więc problem został rozwiązany.

Cytat: Widziałem też sporo projektów, które miały budowanie reklamy w katalogu bin. Czy kompilacja odbywa się w katalogu kompilacji, a następnie pliki binarne są przenoszone do katalogu bin? Czy pliki binarne dla testów i biblioteki będą znajdować się w tym samym miejscu? A może bardziej sensowne byłoby sformułowanie tego w następujący sposób:

Posiadanie katalogu kompilacji poza źródłem (kompilacja „poza kodem”) jest naprawdę jedyną rozsądną rzeczą do zrobienia, jest to obecnie de facto standard. Tak więc na pewno mam oddzielny katalog "build", poza katalogiem źródłowym, tak jak zalecają ludzie z CMake i jak każdy programista, którego kiedykolwiek spotkałem. Jeśli chodzi o katalog bin to cóż, jest to konwencja i chyba warto się jej trzymać, o czym wspomniałem na początku tego wpisu.

Cytat: Chciałbym również użyć doxygen do udokumentowania mojego kodu. Czy możliwe jest automatyczne uruchamianie tego z cmake i make?

Tak. To więcej niż możliwe, jest niesamowite. W zależności od tego, jak fantazyjny chcesz uzyskać, istnieje kilka możliwości. CMake ma moduł dla Doxygen (tj. find_package(Doxygen)), Który pozwala na rejestrowanie celów, które będą uruchamiać Doxygen na niektórych plikach. Jeśli chcesz robić bardziej wymyślne rzeczy, takie jak aktualizowanie numeru wersji w Doxyfile lub automatyczne wprowadzanie znaczników daty / autora dla plików źródłowych i tak dalej, to wszystko jest możliwe dzięki odrobinie kung-fu CMake. Ogólnie rzecz biorąc, zrobienie tego będzie wymagało zachowania źródła Doxyfile (np. „Doxyfile.in”, który umieściłem w układzie folderów powyżej), które zawiera tokeny do znalezienia i zastąpienia przez polecenia parsujące CMake. W moim pliku CMakeLists najwyższego poziomu , znajdziesz jeden taki kawałek kung-fu CMake, który robi kilka wymyślnych rzeczy razem z cmake-doxygen.

Mikael Persson
źródło
Więc main.cpppowinien iść trunk/bin?
Ugnius Malūkas
17

Strukturyzacja projektu

Generalnie wolałbym następujące:

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

Oznacza to, że masz bardzo jasno zdefiniowany zestaw plików API dla swojej biblioteki, a struktura oznacza, że ​​klienci Twojej biblioteki zrobią

#include "project/vector3.hpp"

zamiast mniej wyraźnych

#include "vector3.hpp"


Podoba mi się struktura drzewa / src, aby była zgodna ze strukturą drzewa / include, ale to naprawdę osobiste preferencje. Jeśli jednak twój projekt rozszerza się, aby zawierać podkatalogi w / include / project, ogólnie byłoby pomocne dopasowanie tych w drzewie / src.

Jeśli chodzi o testy, wolę trzymać je „blisko” plików, które testują, a jeśli skończysz z podkatalogami w / src, jest to całkiem łatwy paradygmat dla innych, jeśli chcą znaleźć kod testowy danego pliku.


Testowanie

Po drugie, chciałbym użyć Google C ++ Testing Framework do testowania jednostkowego mojego kodu, ponieważ wydaje się on dość łatwy w użyciu.

Gtest jest rzeczywiście prosty w użyciu i dość wszechstronny pod względem swoich możliwości. Można go bardzo łatwo używać razem z gmockiem, aby rozszerzyć jego możliwości, ale moje własne doświadczenia z gmockiem są mniej korzystne. Jestem całkiem przygotowany, aby zaakceptować, że może to być spowodowane moimi własnymi wadami, ale testy gmocka są zwykle trudniejsze do stworzenia i znacznie bardziej delikatne / trudne do utrzymania. Dużym gwoździem do trumny gmocka jest to, że naprawdę nie gra dobrze z inteligentnymi wskazówkami.

To bardzo banalna i subiektywna odpowiedź na ogromne pytanie (które prawdopodobnie tak naprawdę nie należy do SO)

Czy sugerujesz dołączenie tego do mojego kodu, na przykład w folderze „inc / gtest” lub „contrib / gtest”? Jeśli jest dołączony, czy sugerujesz użycie skryptu fuse_gtest_files.py w celu zmniejszenia liczby lub plików, czy też pozostawienie go tak, jak jest? Jeśli nie w pakiecie, jak jest obsługiwana ta zależność?

Wolę używać ExternalProject_Addmodułu CMake . Pozwala to uniknąć konieczności przechowywania kodu źródłowego gtest w repozytorium lub instalowania go w dowolnym miejscu. Jest automatycznie pobierany i wbudowywany w drzewo kompilacji.

Zobacz moją odpowiedź dotyczącą szczegółów tutaj .

Jeśli chodzi o pisanie testów, jak są one ogólnie zorganizowane? Myślałem o jednym pliku cpp dla każdej klasy (na przykład test_vector3.cpp), ale wszystkie skompilowane do jednego pliku binarnego, aby można je było łatwo uruchomić razem?

Dobry plan.


Budynek

Jestem fanem CMake, ale podobnie jak w przypadku pytań związanych z testami, SO prawdopodobnie nie jest najlepszym miejscem do proszenia o opinie w tak subiektywnej kwestii.

Jak musiałby wyglądać plik CMakeLists.txt, aby mógł zbudować samą bibliotekę lub bibliotekę i testy?

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

Biblioteka pojawi się jako docelowa „ProjectLibrary”, a zestaw testów jako docelowy „ProjectTest”. Określając bibliotekę jako zależność od testowego exe, zbudowanie testowego exe automatycznie spowoduje odbudowę biblioteki, jeśli jest nieaktualna.

Widziałem też sporo projektów, które miały reklamę kompilacji i katalog bin. Czy kompilacja odbywa się w katalogu kompilacji, a następnie pliki binarne są przenoszone do katalogu bin? Czy pliki binarne dla testów i biblioteki będą znajdować się w tym samym miejscu?

CMake zaleca kompilacje „poza źródłem”, tj. Tworzysz własny katalog kompilacji poza projektem i stamtąd uruchamiasz CMake. Pozwala to uniknąć "zanieczyszczania" twojego drzewa źródłowego plikami kompilacji i jest wysoce pożądane, jeśli używasz vcs.

Państwo może określić, że pliki binarne są przeniesione lub skopiowane do innego katalogu raz zbudowany, lub że są domyślnie tworzone w innym katalogu, ale generalnie nie ma potrzeby. CMake zapewnia wszechstronne sposoby instalowania projektu w razie potrzeby lub ułatwia innym projektom CMake „znajdowanie” odpowiednich plików projektu.

W odniesieniu do własnego wsparcia CMake w znajdowaniu i wykonywaniu testów gtest , byłoby to w dużej mierze niewłaściwe, jeśli tworzysz gtest jako część projektu. FindGtestModuł jest rzeczywiście przeznaczony do stosowania w przypadku, gdy został zbudowany numeru GTEST osobno poza projektem.

CMake zapewnia własną strukturę testową (CTest) i idealnie byłoby, gdyby każdy przypadek gtest został dodany jako przypadek CTest.

Jednak GTEST_ADD_TESTSmakro udostępniane przez, FindGtestktóre umożliwia łatwe dodawanie przypadków gtest jako indywidualnych przypadków ctest, jest nieco niedostępne, ponieważ nie działa z makrami gtest innych niż TESTi TEST_F. Value- lub Type parametryzacja testy przy użyciu TEST_P, TYPED_TEST_Pitp nie są obsługiwane w ogóle.

Problem nie ma łatwego rozwiązania, które znam. Najbardziej niezawodnym sposobem uzyskania listy przypadków gtest jest wykonanie pliku exe testowego z flagą --gtest_list_tests. Jednak można to zrobić tylko po skompilowaniu exe, więc CMake nie może tego wykorzystać. Co pozostawia ci dwie możliwości; CMake musi spróbować przeanalizować kod C ++, aby wydedukować nazwy testów (w skrajnym przypadku nietrywialne, jeśli chcesz wziąć pod uwagę wszystkie makra gtest, zakomentowane testy, wyłączone testy) lub przypadki testowe są dodawane ręcznie do Plik CMakeLists.txt.

Chciałbym również użyć doxygen do udokumentowania mojego kodu. Czy możliwe jest automatyczne uruchamianie tego z cmake i make?

Tak - chociaż nie mam doświadczenia na tym froncie. CMake zapewnia FindDoxygenw tym celu.

Fraser
źródło
6

Oprócz innych (znakomitych) odpowiedzi, opiszę strukturę, z której korzystałem w stosunkowo dużych projektach.
Nie mam zamiaru zajmować się pytaniem cząstkowym dotyczącym Doxygena, ponieważ chciałbym tylko powtórzyć to, co zostało powiedziane w innych odpowiedziach.


Racjonalne uzasadnienie

Ze względu na modułowość i łatwość konserwacji projekt jest zorganizowany jako zestaw małych jednostek. Dla jasności nazwijmy je UnitX, gdzie X = A, B, C, ... (ale mogą mieć dowolną ogólną nazwę). Struktura katalogów jest następnie organizowana w celu odzwierciedlenia tego wyboru, z możliwością grupowania jednostek, jeśli to konieczne.

Rozwiązanie

Podstawowy układ katalogów jest następujący (zawartość jednostek jest szczegółowo opisana w dalszej części):

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

project/CMakeLists.txt może zawierać:

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)

i project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

i project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Przejdźmy teraz do struktury różnych jednostek (weźmy na przykład 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]

Do różnych komponentów:

  • Lubię mieć source ( .cpp) i headers ( .h) w tym samym folderze. Pozwala to uniknąć zduplikowanej hierarchii katalogów i ułatwia konserwację. W przypadku instalacji nie ma problemu (zwłaszcza z CMake), aby po prostu filtrować pliki nagłówkowe.
  • Rolą katalogu UnitDjest późniejsze zezwolenie na dołączanie plików z rozszerzeniem #include <UnitD/ClassA.h>. Ponadto podczas instalowania tego urządzenia możesz po prostu skopiować strukturę katalogów w obecnej postaci. Pamiętaj, że możesz również organizować pliki źródłowe w podkatalogach.
  • Podoba mi się READMEplik podsumowujący, o co chodzi w tym urządzeniu i zawierający przydatne informacje na jego temat.
  • CMakeLists.txt może po prostu zawierać:

    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
                          )

    W tym miejscu należy zauważyć, że nie jest konieczne mówienie CMake, że chcemy, aby katalogi dołączane były dla UnitAi UnitC, ponieważ zostało to już określone podczas konfigurowania tych jednostek. Ponadto PUBLICpowie wszystkim zależnym obiektom docelowym, UnitDże powinny automatycznie uwzględniać UnitAzależność, podczas gdy UnitCnie będą wtedy wymagane ( PRIVATE).

  • test/CMakeLists.txt (zobacz poniżej, jeśli chcesz użyć do tego GTest):

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

Korzystanie z GoogleTest

W przypadku Google Test najłatwiej jest, jeśli jego źródło znajduje się gdzieś w katalogu źródłowym, ale nie musisz go tam dodawać samodzielnie. Używałem tego projektu do automatycznego pobierania i zawijam jego użycie w funkcję, aby upewnić się, że zostanie pobrany tylko raz, mimo że mamy kilka celów testowych.

Ta funkcja CMake jest następująca:

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

a następnie, gdy chcę go użyć w jednym z moich celów testowych, dodam następujące wiersze do CMakeLists.txt(tak jest w powyższym przykładzie test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)
oLen
źródło
Niezły hack, który zrobiłeś tam z Gtestem i cmake! Przydatny! :)
Tanasis