Organizzazione del progetto C ++ (con gtest, cmake e doxygen)


123

Sono nuovo nella programmazione in generale, quindi ho deciso di iniziare creando una semplice classe vettoriale in C ++. Tuttavia, vorrei entrare in buone abitudini dall'inizio piuttosto che provare a modificare il mio flusso di lavoro in seguito.

Al momento ho solo due file vector3.hppe vector3.cpp. Questo progetto inizierà lentamente a crescere (rendendolo molto più di una libreria di algebra lineare generale) man mano che acquisisco familiarità con tutto, quindi vorrei adottare un layout di progetto "standard" per semplificare la vita in seguito. Quindi dopo essermi guardato intorno ho trovato due modi per organizzare i file hpp e cpp, il primo è:

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

e il secondo essere:

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

Quale consiglieresti e perché?

In secondo luogo, vorrei utilizzare Google C ++ Testing Framework per testare il mio codice in quanto sembra abbastanza facile da usare. Suggerite di raggrupparlo con il mio codice, ad esempio in una cartella inc/gtesto contrib/gtest? Se in bundle, suggerisci di utilizzare lo fuse_gtest_files.pyscript per ridurre il numero di file o di lasciarlo così com'è? Se non in bundle come viene gestita questa dipendenza?

Quando si tratta di scrivere test, come sono generalmente organizzati? Stavo pensando di avere un file cpp per ogni classe ( test_vector3.cppad esempio) ma tutti compilati in un binario in modo che possano essere eseguiti tutti insieme facilmente?

Dato che la libreria gtest è generalmente costruita usando cmake e make, stavo pensando che avrebbe senso che anche il mio progetto fosse costruito in questo modo? Se ho deciso di utilizzare il seguente layout di progetto:

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

Come dovrebbero CMakeLists.txtapparire in modo da poter costruire solo la libreria o la libreria e i test? Inoltre ho visto alcuni progetti che hanno builduna bindirectory e. La compilazione avviene nella directory build e quindi i binari vengono spostati nella directory bin? I binari per i test e la libreria risiederebbero nello stesso posto? Oppure avrebbe più senso strutturarlo come segue:

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

Vorrei anche usare doxygen per documentare il mio codice. È possibile farlo funzionare automaticamente con cmake e make?

Ci scusiamo per così tante domande, ma non ho trovato un libro su C ++ che risponda in modo soddisfacente a questo tipo di domande.


6
Ottima domanda, ma non credo che sia adatto per il formato Q&A di Stack Overflow . Sono molto interessato a una risposta però. +1 e fav
Luchian Grigore

1
Queste sono molte domande enormi. Potrebbe essere meglio dividerlo in diverse domande più piccole e inserire collegamenti tra loro. Comunque per rispondere all'ultima parte: con CMake puoi scegliere di costruire dentro e fuori la tua directory src (io consiglierei fuori). E sì, puoi usare doxygen con CMake automaticamente.
mistapink

Risposte:


84

I sistemi di compilazione C ++ sono un po 'un'arte nera e più vecchio è il progetto più cose strane puoi trovare, quindi non sorprende che sorgano molte domande. Cercherò di esaminare le domande una per una e di menzionare alcune cose generali riguardanti la creazione di librerie C ++.

Separazione di intestazioni e file cpp nelle directory. Questo è essenziale solo se stai costruendo un componente che dovrebbe essere usato come libreria invece di un'applicazione reale. Le tue intestazioni sono la base per l'interazione degli utenti con ciò che offri e devono essere installate. Ciò significa che devono trovarsi in una sottodirectory (nessuno vuole che molte intestazioni finiscano nel livello superiore /usr/include/) e le tue intestazioni devono essere in grado di includersi con tale configurazione.

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

funziona bene, perché i percorsi di inclusione funzionano e puoi usare il globbing facile per le destinazioni di installazione.

Dipendenze raggruppate: penso che ciò dipenda in gran parte dalla capacità del sistema di compilazione di individuare e configurare le dipendenze e da quanto sia dipendente il codice da una singola versione. Dipende anche da quanto sono abili i tuoi utenti e quanto è facile la dipendenza da installare sulla loro piattaforma. CMake viene fornito con uno find_packagescript per Google Test. Questo rende le cose molto più facili. Andrei con il raggruppamento solo quando necessario e lo eviterei altrimenti.

Come costruire: evitare build in-source. CMake rende facili le build dei sorgenti e rende la vita molto più semplice.

Suppongo che tu voglia usare CTest anche per eseguire test per il tuo sistema (include anche il supporto integrato per GTest). Una decisione importante per il layout delle directory e l'organizzazione dei test sarà: finisci con i sottoprogetti? Se è così, hai bisogno di un po 'di lavoro in più quando imposti CMakeList e dovresti dividere i tuoi sottoprogetti in sottodirectory, ciascuna conincludesrc file e . Forse anche le proprie corse e uscite doxygen (combinare più progetti doxygen è possibile, ma non è facile o carino).

Finirai con qualcosa del genere:

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

dove

  • (1) configura dipendenze, specifiche della piattaforma e percorsi di output
  • (2) configura la libreria che stai per costruire
  • (3) configura gli eseguibili di test e i casi di test

Nel caso in cui si disponga di sottocomponenti, suggerirei di aggiungere un'altra gerarchia e utilizzare l'albero sopra per ogni sottoprogetto. Quindi le cose si complicano, perché devi decidere se i sottocomponenti cercano e configurano le loro dipendenze o se lo fai nel livello superiore. Questo dovrebbe essere deciso caso per caso.

Doxygen: Dopo che sei riuscito a passare attraverso la danza di configurazione di doxygen, è banale usare CMake add_custom_command per aggiungere un documento di destinazione.

È così che finiscono i miei progetti e ho visto alcuni progetti molto simili, ma ovviamente questo non è un rimedio.

Addendum Ad un certo punto si vorrà generare un config.hpp file che contenga una definizione di versione e forse una definizione di un identificatore di controllo della versione (un hash Git o un numero di revisione SVN). CMake dispone di moduli per automatizzare la ricerca di tali informazioni e generare file. È possibile utilizzare CMake configure_fileper sostituire le variabili in un file modello con variabili definite all'interno del file CMakeLists.txt.

Se stai costruendo librerie avrai anche bisogno di una definizione di esportazione per ottenere la differenza tra i compilatori, ad esempio __declspecsu MSVC e visibilityattributi su GCC / clang.


2
Bella risposta, ma non sono ancora chiaro il motivo per cui è necessario inserire i file di intestazione in una sottodirectory aggiuntiva denominata progetto: "/prj/include/prj/foo.hpp", che mi sembra ridondante. Perché non solo "/prj/include/foo.hpp"? Presumo che avrai l'opportunità di modificare nuovamente le directory di installazione al momento dell'installazione, quindi ottieni <INSTALL_DIR> /include/prj/foo.hpp quando installi, o è difficile sotto CMake?
William Payne

6
@William Questo è effettivamente difficile da fare con CPack. Inoltre, come sarebbero i tuoi include all'interno dei file sorgente? Se sono solo "header.hpp" su una versione installata "/ usr / include / prj /" deve essere nel percorso di inclusione invece di "/ usr / include".
pmr

37

Per cominciare, ci sono alcuni nomi convenzionali per le directory che non puoi ignorare, questi sono basati sulla lunga tradizione con il file system Unix. Questi sono:

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

Probabilmente è una buona idea attenersi a questo layout di base, almeno al livello più alto.

Per quanto riguarda la divisione dei file di intestazione e dei file sorgente (cpp), entrambi gli schemi sono abbastanza comuni. Tuttavia, tendo a preferire tenerli insieme, è solo più pratico nelle attività quotidiane avere i file insieme. Inoltre, quando tutto il codice si trova in una cartella di primo livello, ovvero il filetrunk/src/ cartella, puoi notare che tutte le altre cartelle (bin, lib, include, doc e forse qualche cartella di test) al livello superiore, oltre a la directory "build" per una build out-of-source, sono tutte le cartelle che non contengono altro che file generati nel processo di build. E quindi, solo la cartella src deve essere sottoposta a backup, o molto meglio, mantenuta sotto un sistema / server di controllo della versione (come Git o SVN).

E quando si tratta di installare i file di intestazione sul sistema di destinazione (se si desidera eventualmente distribuire la libreria), beh, CMake ha un comando per l'installazione dei file (crea implicitamente una destinazione di "installazione", per fare "make install") che puoi usare per mettere tutte le intestazioni nella /usr/include/directory. Uso solo la seguente macro cmake per questo scopo:

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

Dov'è SRCROOTuna variabile cmake che ho impostato nella cartella src, ed INCLUDEROOTè una variabile cmake che configuro dove devono andare le intestazioni. Naturalmente, ci sono molti altri modi per farlo, e sono sicuro che il mio non sia il migliore. Il punto è che non c'è motivo di dividere le intestazioni e le fonti solo perché solo le intestazioni devono essere installate sul sistema di destinazione, perché è molto facile, specialmente con CMake (o CPack), scegliere e configurare le intestazioni per essere installato senza doverli avere in una directory separata. E questo è quello che ho visto nella maggior parte delle biblioteche.

Quote: In secondo luogo, vorrei utilizzare Google C ++ Testing Framework per testare il mio codice in quanto sembra abbastanza facile da usare. Suggerite di raggrupparlo con il mio codice, ad esempio in una cartella "inc / gtest" o "contrib / gtest"? Se in bundle, suggerisci di utilizzare lo script fuse_gtest_files.py per ridurre il numero di file o di lasciarlo così com'è? Se non in bundle come viene gestita questa dipendenza?

Non raggruppare le dipendenze con la tua libreria. Questa è generalmente un'idea piuttosto orribile, e odio sempre quando sono bloccato nel tentativo di costruire una libreria che lo ha fatto. Dovrebbe essere la tua ultima risorsa e fai attenzione alle insidie. Spesso, le persone raggruppano le dipendenze con la loro libreria o perché prendono di mira un terribile ambiente di sviluppo (ad esempio, Windows), o perché supportano solo una vecchia versione (deprecata) della libreria (dipendenza) in questione. La trappola principale è che la tua dipendenza in bundle potrebbe scontrarsi con le versioni già installate della stessa libreria / applicazione (ad esempio, hai raggruppato gtest, ma la persona che cerca di costruire la tua libreria ha già una versione più recente (o precedente) di gtest già installata, quindi i due potrebbero scontrarsi e dare a quella persona un brutto mal di testa). Quindi, come ho detto, fallo a tuo rischio, e direi solo come ultima risorsa. Chiedere alle persone di installare alcune dipendenze prima di essere in grado di compilare la tua libreria è un male minore rispetto al tentativo di risolvere i conflitti tra le dipendenze in bundle e le installazioni esistenti.

Quote: Quando si tratta di scrivere test, come sono generalmente organizzati? Stavo pensando di avere un file cpp per ogni classe (test_vector3.cpp per esempio) ma tutto compilato in un binario in modo che possano essere eseguiti tutti insieme facilmente?

Un file cpp per classe (o un piccolo gruppo coeso di classi e funzioni) è più usuale e pratico secondo me. Tuttavia, sicuramente, non compilarli tutti in un binario solo in modo che "possano essere eseguiti tutti insieme". Questa è davvero una pessima idea. In generale, quando si tratta di codifica, si desidera dividere le cose per quanto è ragionevole farlo. Nel caso degli unit-test, non vuoi che un binario esegua tutti i test, perché ciò significa che qualsiasi piccola modifica che fai a qualcosa nella tua libreria probabilmente causerà una ricompilazione quasi totale di quel programma di unit-test , e sono solo minuti / ore persi in attesa di ricompilazione. Attenersi a uno schema semplice: 1 unità = 1 programma di test unitario. Poi,

Quote: Dato che la libreria gtest è generalmente costruita usando cmake e make, stavo pensando che avrebbe senso che anche il mio progetto fosse costruito in questo modo? Se ho deciso di utilizzare il seguente layout di progetto:

Preferirei suggerire questo layout:

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

Alcune cose da notare qui. Primo, le sottodirectory della tua directory src dovrebbero rispecchiare le sottodirectory della tua directory include, questo è solo per mantenere le cose intuitive (inoltre, prova a mantenere la struttura della tua sottodirectory ragionevolmente piatta (superficiale), perché l'annidamento profondo delle cartelle è spesso più una seccatura che altro). In secondo luogo, la directory "include" è solo una directory di installazione, i suoi contenuti sono solo le intestazioni selezionate dalla directory src.

Terzo, il sistema CMake è pensato per essere distribuito nelle sottodirectory di origine, non come un file CMakeLists.txt al livello superiore. Ciò mantiene le cose locali, e questo è un bene (nello spirito di dividere le cose in pezzi indipendenti). Se aggiungi una nuova sorgente, una nuova intestazione o un nuovo programma di test, tutto ciò di cui hai bisogno è modificare un piccolo e semplice file CMakeLists.txt nella sottodirectory in questione, senza influire su nient'altro. Ciò consente anche di ristrutturare facilmente le directory (le CMakeList sono locali e sono contenute nelle sottodirectory che vengono spostate). Le CMakeList di primo livello dovrebbero contenere la maggior parte delle configurazioni di primo livello, come l'impostazione di directory di destinazione, comandi personalizzati (o macro) e la ricerca di pacchetti installati sul sistema. Le CMakeLists di livello inferiore dovrebbero contenere solo semplici elenchi di intestazioni, fonti,

Quote: Come dovrebbe apparire il CMakeLists.txt in modo che possa costruire solo la libreria o la libreria e i test?

La risposta di base è che CMake ti consente di escludere specificamente determinati target da "all" (che è ciò che viene creato quando digiti "make"), e puoi anche creare bundle specifici di target. Non posso fare un tutorial su CMake qui, ma è abbastanza semplice scoprirlo da solo. In questo caso specifico, tuttavia, la soluzione consigliata è, ovviamente, utilizzare CTest, che è solo un set aggiuntivo di comandi che è possibile utilizzare nei file CMakeLists per registrare una serie di target (programmi) contrassegnati come unit- test. Quindi, CMake metterà tutti i test in una categoria speciale di build, ed è esattamente quello che hai chiesto, quindi, problema risolto.

Quote: Inoltre ho visto alcuni progetti che hanno un annuncio di build una directory bin. La compilazione avviene nella directory build e quindi i binari vengono spostati nella directory bin? I binari per i test e la libreria risiederebbero nello stesso posto? Oppure avrebbe più senso strutturarlo come segue:

Avere una directory di compilazione al di fuori dei sorgenti (build "out-of-source") è davvero l'unica cosa sensata da fare, è lo standard di fatto in questi giorni. Quindi, sicuramente, avere una directory di "build" separata, al di fuori della directory dei sorgenti, proprio come consigliano le persone di CMake e come fa ogni programmatore che abbia mai incontrato. Per quanto riguarda la directory bin, beh, questa è una convenzione, ed è probabilmente una buona idea attenersi ad essa, come ho detto all'inizio di questo post.

Quote: Vorrei anche usare doxygen per documentare il mio codice. È possibile farlo funzionare automaticamente con cmake e make?

Sì. È più che possibile, è fantastico. A seconda di quanto desideri ottenere, ci sono diverse possibilità. CMake ha un modulo per Doxygen (cioè find_package(Doxygen)) che ti permette di registrare obiettivi che eseguiranno Doxygen su alcuni file. Se vuoi fare cose più fantasiose, come aggiornare il numero di versione nel Doxyfile, o inserire automaticamente un timbro data / autore per i file sorgente e così via, tutto è possibile con un po 'di CMake kung-fu. Generalmente, fare questo comporterà la conservazione di un Doxyfile sorgente (ad esempio, il "Doxyfile.in" che ho inserito nel layout della cartella sopra) che deve trovare i token e sostituirli con i comandi di analisi di CMake. Nel mio file CMakeLists di primo livello , troverai uno di questi pezzi di CMake kung-fu che fa alcune cose fantasiose con cmake-doxygen insieme.


Quindi main.cppdovrebbe andare a trunk/bin?
Ugnius Malūkas

17

Strutturare il progetto

In genere preferirei quanto segue:

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

Ciò significa che hai un insieme molto chiaramente definito di file API per la tua libreria e la struttura significa che i client della tua libreria farebbero

#include "project/vector3.hpp"

piuttosto che il meno esplicito

#include "vector3.hpp"


Mi piace che la struttura dell'albero / src corrisponda a quella dell'albero / include, ma questa è davvero una preferenza personale. Tuttavia, se il tuo progetto si espande per contenere sottodirectory all'interno di / include / project, generalmente sarebbe utile abbinare quelle all'interno dell'albero / src.

Per i test, preferisco tenerli "vicini" ai file che testano, e se finisci con le sottodirectory all'interno di / src, è un paradigma abbastanza facile da seguire per gli altri se vogliono trovare il codice di test di un determinato file.


analisi

In secondo luogo, vorrei utilizzare Google C ++ Testing Framework per testare il mio codice in quanto sembra abbastanza facile da usare.

Gtest è davvero semplice da usare ed è abbastanza completo in termini di capacità. Può essere utilizzato insieme a gmock molto facilmente per estendere le sue capacità, ma le mie esperienze con gmock sono state meno favorevoli. Sono abbastanza pronto ad accettare che questo potrebbe dipendere dai miei difetti, ma i test gmock tendono ad essere più difficili da creare e molto più fragili / difficili da mantenere. Un grande chiodo nella bara di gmock è che non funziona davvero bene con i puntatori intelligenti.

Questa è una risposta molto banale e soggettiva a una domanda enorme (che probabilmente non appartiene a SO)

Suggerite di raggrupparlo con il mio codice, ad esempio in una cartella "inc / gtest" o "contrib / gtest"? Se in bundle, suggerisci di utilizzare lo script fuse_gtest_files.py per ridurre il numero di file o di lasciarlo così com'è? Se non in bundle come viene gestita questa dipendenza?

Preferisco usare CMake's ExternalProject_Add modulo . Questo evita di dover mantenere il codice sorgente di gtest nel tuo repository o installarlo ovunque. Viene scaricato e integrato automaticamente nel tuo albero di compilazione.

Vedi la mia risposta che tratta le specifiche qui .

Quando si tratta di scrivere test, come sono generalmente organizzati? Stavo pensando di avere un file cpp per ogni classe (test_vector3.cpp per esempio) ma tutto compilato in un binario in modo che possano essere eseguiti tutti insieme facilmente?

Buon piano.


Edificio

Sono un fan di CMake, ma come per le tue domande relative ai test, SO probabilmente non è il posto migliore per chiedere opinioni su una questione così soggettiva.

Come dovrebbe apparire il CMakeLists.txt in modo che possa costruire solo la libreria o la libreria e i test?

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

La libreria apparirà come destinazione "ProjectLibrary" e la suite di test come destinazione "ProjectTest". Specificando la libreria come una dipendenza dell'exe di test, la creazione dell'exe di test provocherà automaticamente la ricostruzione della libreria se non è aggiornata.

Inoltre ho visto alcuni progetti che hanno un annuncio di compilazione una directory bin. La compilazione avviene nella directory build e quindi i binari vengono spostati nella directory bin? I binari per i test e la libreria risiederebbero nello stesso posto?

CMake consiglia build "out-of-source", ovvero si crea la propria directory di build al di fuori del progetto e si esegue CMake da lì. Ciò evita di "inquinare" il tuo albero dei sorgenti con i file di build ed è altamente desiderabile se stai usando un vcs.

È possibile specificare che i file binari vengano spostati o copiati in una directory diversa una volta creati, o che vengano creati per impostazione predefinita in un'altra directory, ma in genere non è necessario. CMake fornisce modi completi per installare il tuo progetto, se lo desideri, o per rendere più facile per altri progetti CMake "trovare" i file rilevanti del tuo progetto.

Per quanto riguarda il supporto di CMake per la ricerca e l'esecuzione di test gtest , ciò sarebbe in gran parte inappropriato se si crea gtest come parte del progetto. IlFindGtest modulo è davvero progettato per essere utilizzato nel caso in cui gtest sia stato costruito separatamente al di fuori del tuo progetto.

CMake fornisce il proprio framework di test (CTest) e, idealmente, ogni caso gtest verrebbe aggiunto come caso CTest.

Tuttavia, la GTEST_ADD_TESTSmacro fornita da FindGtestper consentire una facile aggiunta di casi gtest come singoli casi ctest è piuttosto carente in quanto non funziona per le macro di gtest diverse da TESTe TEST_F. Value- o Type-parametrizzati test utilizzando TEST_P, TYPED_TEST_Pecc non vengono gestiti a tutti.

Il problema non ha una soluzione facile che io sappia. Il modo più efficace per ottenere un elenco di casi di gtest è eseguire l'exe di test con il flag --gtest_list_tests. Tuttavia, questo può essere fatto solo dopo aver creato l'exe, quindi CMake non può utilizzarlo. Il che ti lascia con due scelte; CMake deve provare ad analizzare il codice C ++ per dedurre i nomi dei test (non banale all'estremo se si desidera prendere in considerazione tutte le macro gtest, i test commentati, i test disabilitati), oppure i casi di test vengono aggiunti manualmente al File CMakeLists.txt.

Vorrei anche usare doxygen per documentare il mio codice. È possibile farlo funzionare automaticamente con cmake e make?

Sì, anche se non ho esperienza su questo fronte. CMake prevede FindDoxygenquesto scopo.


6

Oltre alle altre (eccellenti) risposte, descriverò una struttura che ho utilizzato per progetti su scala relativamente ampia .
Non ho intenzione di affrontare la sottoquestione su Doxygen, poiché vorrei semplicemente ripetere quanto detto nelle altre risposte.


Fondamento logico

Per modularità e manutenibilità, il progetto è organizzato come un insieme di piccole unità. Per chiarezza, chiamiamoli UnitX, con X = A, B, C, ... (ma possono avere qualsiasi nome generico). La struttura della directory viene quindi organizzata per riflettere questa scelta, con la possibilità di raggruppare le unità se necessario.

Soluzione

Il layout di directory di base è il seguente (il contenuto delle unità viene descritto in dettaglio più avanti):

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

project/CMakeLists.txt potrebbe contenere quanto segue:

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)

e project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

e project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Passiamo ora alla struttura delle diverse unità (prendiamo, ad esempio, 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]

Ai diversi componenti:

  • Mi piace avere source ( .cpp) e header (.h ) nella stessa cartella. Ciò evita una gerarchia di directory duplicata e semplifica la manutenzione. Per l'installazione, non è un problema (specialmente con CMake) filtrare solo i file di intestazione.
  • Il ruolo della directory UnitDè di consentire in seguito l'inclusione di file con estensione#include <UnitD/ClassA.h> . Inoltre, quando si installa questa unità, è possibile copiare semplicemente la struttura della directory così com'è. Tieni presente che puoi anche organizzare i tuoi file sorgente in sottodirectory.
  • Mi piace un READMEfile per riassumere di cosa tratta l'unità e specificare informazioni utili a riguardo.
  • CMakeLists.txt potrebbe semplicemente contenere:

    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
                          )

    Qui, nota che non è necessario dire a CMake che vogliamo le directory di inclusione per UnitAe UnitC, poiché questo è stato già specificato durante la configurazione di quelle unità. Inoltre, PUBLICdirà a tutti gli obiettivi che dipendono dal UnitDfatto che dovrebbero includere automaticamente la UnitAdipendenza, mentre UnitCnon sarà richiesto then ( PRIVATE).

  • test/CMakeLists.txt (vedi più avanti se vuoi usare GTest per questo):

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

Utilizzando GoogleTest

Per Google Test, il più semplice è se la sua fonte è presente da qualche parte nella tua directory di origine, ma non devi aggiungerla effettivamente lì da solo. Sto usando questo progetto per scaricarlo automaticamente e ne racchiudo l'utilizzo in una funzione per assicurarmi che venga scaricato una sola volta, anche se abbiamo diversi obiettivi di test.

Questa funzione CMake è la seguente:

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

e poi, quando voglio usarlo all'interno di uno dei miei obiettivi di test, aggiungerò le seguenti righe a CMakeLists.txt(questo è per l'esempio sopra, test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)

Bel "trucco" che hai fatto lì con Gtest e cmake! Utile! :)
Tanasis
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.