Abstract
Sono sicuro che ogni programmatore C++ ha, ad un certo punto, dovuto scontrarsi con CMake. Mi è capitato spesso, nei primi progetti, dilungarmi per troppo tempo all’organizzazione della struttura del progetto. È una gran seccatura dover scrivere e tenere seotto controllo ogni file CMakeList.txt sparso nelle directories, e non scordiamoci di tutte le librerie e moduli che possono creare errori nel processo di compilazione se male impostati.
Dopo aver analizzato varie pratiche personali di altri sviluppatori, sono finalmente riuscito a trovare una struttura che mi compiace, per semplicità e chiarezza.
Ci tengo a precisare che Le informazioni che seguono sono di parte. La è concepita per:
- Evitare schemi che causano conflitti.
- Evitare di complicare la compilazione.
- Semplificare la lettura.
Cosa ci serve?
Per strutturare il progetto abbiamo necessità di tre tools molto importanti.
CMake
CMake è un software libero multipiattaforma per l’automazione dello sviluppo il cui nome è un’abbreviazione di cross platform make. Questo software nasce per rimpiazzare Automake nella generazione dei Makefile, cercando di essere più semplice da usare. Infatti, nella maggior parte dei progetti, non esiste un Makefile incluso nei sorgenti, dato che questo non è portabile.
– Wikipedia
Ci servirà per la generazione degli script, della build e la compilazione del progetto.
Non fornirò una guida completa su CMake, perchè l’articolo uscirebbe troppo complesso. Tuttavia se siete interessati allo strumento cliccando qui verrete indirizzati al sito di CMake.
Conan
Conan is a dependency and package manager for C and C++ languages. It is free and open-source, works in all platforms ( Windows, Linux, OSX, FreeBSD, Solaris, etc.), and can be used to develop for all targets including embedded, mobile (iOS, Android), and bare metal. It also integrates with all build systems like CMake, Visual Studio (MSBuild), Makefiles, SCons, etc., including proprietary ones.
– Conan
Ci servirà per la gestione delle dipendenze e dei pacchetti.
Se vi interessa lo strumento cliccando qui verrete indirizzati al sito di Conan.
Prima di procedere vorrei illustrarvi i passi fondamentali per utilizzare Conan.
Si inizia generando un profilo Conan, il quale consente di definire un insieme di configurazioni per elementi quali il compilatore, la configurazione di compilazione, l’architettura, ecc. Lo facciamo con il comando successivo.
|
|
Al termine dell’esecuzione, se siete su Unix, troverete nella home directory una cartella .conan2
che conterrà i files sopracitati.
Per installare le dipendenze ustiamo la seguente istruzione.
|
|
Conan quindi esegue due operazioni fondamentali:
- Installa le librerie specificate nel conanfile.txt dal server remoto, che dovrebbe essere il server Conan Center, se disponibili. Questo server memorizza sia le Conan recipies, che definiscono come devono essere costruite le librerie, sia i binaries che possono essere riutilizzati in modo da non doverli ogni volta ricompilarli.
- Genera diversi files nella directory build.
- CMakeDeps genera i file necassari per far si che CMake trovi le librerie che abbiamo scaricato.
- CMakeToolchain genera un file toolchain per CMake per poter costruire il nostro progetto con CMake.
Doxygen
Doxygen è una applicazione per la generazione automatica della documentazione a partire dal codice sorgente di un generico software. È un progetto open source disponibile sotto licenza GPL, scritto per la maggior parte da Dimitri van Heesch a partire dal 1997.
– Wikipedia
Per la generazione della documentazione del codice. Documentate sempre mi raccomando!
Come per gli strumenti precedenti non fornirò una guida completa su Doxygen, cliccando qui verrete indirizzati al sito di Doxygen.
Struttura
La struttura del progetto ha la seguente forma.
|
|
Cerchiamo però di esporla meglio.
L’idea di fondo è quella di separare per directory i vari componenti del progetto. Ogni directory contiene un eseguibile o una libreria. In questo modo andremo a separare o meglio effettuare una compartimentazione dei vari target.
Standalone, un target eseguibile. Nel caso in questione utilizza la libreria libfoo. Esso è concepito con l’idea di essere un applicazione che utilizza una o più librerie “core” (che gli permettono di funzionare).
Libfoo in questo caso è una libreria “core” per standalone, all’occhio una semplice libreria statica, ma più attentamente si vede come sia stata costrutita come un progetto a se stante. Ogni libreria dovrà:
- Seguire lo standard canonico della struttura di una libreria standard.
- Directory
include
per le dichiarazioni pubbliche, interfaccia della libreria. - Directory
src
per le definizioni e per headers privati.
- Directory
- Garantire la generazione della documentazione con Doxygen.
- Fornire un ambiente di testing con doctest (o similari).
Per comprendere meglio utilizziamo l’immagine sottostante come esempio. Si noti come gli eseguibili “app” utilizzino delle librerie “core”.
TopLevel CMakeLists.txt
Il file CMakeLists.txt nella root contiene la configurazione toplevel del nostro progetto di cui il contenuto è il seguente.
|
|
Abbiamo un solo progetto e specifichiamo a CMake di ricercare altri file di configurazione nelle sottocartelle libfoo e standalone, rispettivamente “core” e “app”.
Specifichiamo poi delle semplici variabili, ed impartiamo dei comandi find_package
per ricercare le dipendenze necessarie al processo di compilazione.
Gestione dipendenze
Il file conanfile.txt
specifica i pacchetti che serviranno ai vari target del nostro progetto. Nel nostro caso fmt viene usato sia da
libfoo che da standalone, doctest viene richiesto da libfoo per gli unit tests.
Il contenuto di seguito.
|
|
Per usufruire meglio di conan utilizziamo il wrapper cmake-conan, in particolare
siamo interessati al file conan_provider.cmake
che salviamo nella root del progetto.
Questo file ci tornerà utile in seguito.
Target libfoo
Il target libfoo che nel caso in analisi si presenta come una libreria statica, segue la forma canonica di una libreria standard. Ritornando al concetto di un target per subdirectory il nostro CMakeLists.txt è il sottostante.
|
|
Abbiamo tre sezioni:
- Definizione della libreria e della sua configurazione.
- Impostazione e creazione del target per gli unit tests.
- Abilitazione e configurazione di Doxygen per la generazione della documentazione.
Per chiarezza riporto il conenuto del CMakeLists.txt nella cartella docs.
|
|
Target standalone
A differenza del target libfoo, questo risulta molto più banale di seguito la configurazione.
|
|
Compilazione
|
|
Con il primo comando eseguiamo la fase di configurazione out-of-tree, generando un sistema di compilazione. Con il secondo costruiamo il progetto chiamando lo strumento di compilazione di sistema, make su Unix.
Ricordate il file conan_provider.cmake
enunciato in precedenza?
Ebbene utilizzando l’impostazione -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake
nel processo di configurazione di cmake,
questo invoca automaticamente il comando conan install
semplificandoci la gestione del progetto.
Conclusione
Ora nella cartella build sotto la root del progetto troveremo i file binari e le librerie che abbiamo compilato, oltre che alla documentazione doxygen e all’eseguibile degli unit tests.
Questa struttura può facilmente essere apliate aggiungendo il pipeline CI/CD e altro.
Spero di esservi stato utile, a questo link trovate la repository del progetto discusso in questo articolo in maniera che possiate analizzarlo.