venerdì 19 dicembre 2014

Modern C++ - Lavorare con CMake

Ben ritornati a questo nostro appuntamento con il Modern C++. Fin qui, a dir il vero, abbiamo considerato pochissimo C++, soffermandoci soprattutto sui tools che ruotano intorno ad esso.

Il filo conduttore di questo corso è quello di imparare a sviluppare un progetto partendo dalle fondamenta, utilizzando un approccio bottom-up.

Nella precedente puntata, abbiamo visto come la direttiva #include consenta di importare un header file all'interno di un altro file.
Pensiamo per un momento ad un ipotetica idea di progetto: ad esempio una calcolatrice, oppure un editor di testo sulla falsa riga di gedit oppure di un gioco. Nel caso di un gioco, si ha a che fare con diversi componenti: i personaggi del gioco, la mappa, i suoni, i nemici; possiamo chiamare questi componenti moduli, che potrebbero essere definiti una volta ed essere riutilizzati per differenti progetti (ricordate, uno dei principi fondamentali di ogni linguaggio orientato agli oggetti come il C++ è il DRY - Don't Repeat Yourself, non ripetere ciò che hai giá fatto).

E' possibile automatizzare il processo di gestione dei moduli e delle applicazioni grazie ad un corretto setup di CMake.
L'idea base è la seguente:

Fig. 1 - Esempio di Applicazioni modulari
Immaginate di avere alcuni moduli fondamentali (come in figura 1, i moduli 3 e 4), dai quali dipendono a loro volta altri moduli. A partire da questa struttura di moduli, è possibile poi costruire svariate applicazioni (pensiamo a differenti applicazioni con grafica diversa, piattaforma di riferimento diversa).
In C++, questi moduli saranno chiamati librerie, poichè al loro interno conterranno le definizioni di funzioni che verranno utilizzate dalle applicazioni finali come se fossero dei libri di ricette culinarie già pronte.

Al fine di realizzare questa automatizzazione con CMake, occorre creare una struttura simile alla seguente:
Fig. 2 - Creazione di una struttura di progetto
Come potete osservare dalla Fig. 2, occorre definire una directory principale, tipicamente con il nome del progetto, in questo caso ModernCpp, e successivamente occorre definire le seguenti 3 subdirectories:
  • Applications, contenente a sua volta tante directories quante sono le applicazioni che vogliamo realizzare,
  • Modules, contenente i moduli che vogliamo sviluppare
  • CMakeModules, che raccoglierá specifici files *.cmake utili per gestire le dipendenze con altre librerie di terze parti (ad esempio, Qt) e proprietá di progetto (eventuali flags per il compilatore, informazioni su come generare un progetto redistribuibile)
Inoltre, abbiamo il bisogno di definire tanti file CMakeLists.txt quante sono le macro-aree di lavoro: un file CMakeLists.txt generale, che conterrà i riferimenti ai CMakeLists.txt delle directory Modules ed Applications, i quali file CMakeLists.txt conterranno a loro volta riferimento ai file di configurazione dei singoli moduli o delle singole applicazioni.

Analizziamo con ordine i singoli file CMakeLists.txt, al fine di capire meglio come funziona questo setup.

CMakeLists.txt principale

Il file CMakeLists.txt principale è organizzato come segue:
A differenza del primo file CMakeLists.txt visto in precedenza, questa volta il file è stato realizzato manualmente.
Le linee 1-13 definiscono la versione minima di CMake installata, il nome del progetto, "ModernCpp", il tipo di linguaggio utilizzato è C++ (identificato con CXX) e definiamo alcune variabili, contenenti le informazioni sulla versione del nostro progetto, ad esempio v. 0.1.0 (siamo solo agli inizi!).
Al rigo 16, importiamo un file di configurazione di progetto (per il momento vuoto) ed infine definiamo un prefisso per il nome dei nostri moduli e le directories da esplorare per ulteriori file CMakeLists.txt.

CMakeLists.txt Modules/

Poiché siamo ancora agli inizi, i nostri moduli sono ancora privi di funzionalitá.
Il file CMakeLists.txt della directory Modules per il momento è molto semplice ed ha il seguente contenuto:
semplicemente stiamo definendo le sottodirectory in cui cercare altri file CMakeLists.txt.
I singoli CMakeLists.txt dei sottomoduli non sono qui riportarti, poichè privi di funzionalitá (non contengono alcun codice C++).

CMakeLists.txt Applications/

Il file CMakeLists.txt contenuto nella directory Applications è simile a quello della directory Modules:
mentre è interessante notare il contenuto delle singole DemoApp definite.

CMakeLists.txt Applications/DemoApp1 (2 o 3)

Il file CMakeLists.txt della singola applicazione è un po' piú complesso dei precedenti. Nelle prime linee, definiamo il nome della applicazione, utilizzando come prefisso, il nome specificato nel file CMakeLists.txt della root directory del progetto e diamo ancora istruzioni sul tipo di progetto, ovvero di considerare il codice sorgente come codice C++.
Le linee 6-9 consentono di sopprimere warnings causati dalla versione 3.0 e superiore di CMake, che compaiono con l'integrazione di alcune dipendenze. Per il momento, è possibile ignorarle.
Le linee 15 e 16 consentono di considerare le sottodirectories include/ e src/ come percorsi in cui cercare gli header files e i source files dell'applicazione in questione, da poter utilizzare successivamente con la direttiva #include. Questi sono interpretati come percorsi relativi al file CMakeLists.txt corrente e non relativi a quelli contenuti in altre directories.
Le linee 18 e 19 invece si occupano di cercare ricorsivamente tutti i file che hanno estensione *.h, contenuti nella directory include, e di salvarli nella variabile ${APPLICATION_NAME}_HEADERS (simil cosa avviene per i file *.cpp in src/).
La linea 25 si occupa di generare l'eseguibile partendo dai file specificati nelle variabili ${APPLICATION_NAME}_HEADERS e ${APPLICATION_NAME}_SOURCES, oltre al file main.cpp.
Infine, la linea 31 si occupa di collegare (linkare) eventuali librerie al nostro eseguibile. Per il momento, il comando non esegue alcuna operazione, dato che non abbiamo definito queste librerie.

Test del nostro Progetto

Su GitHub ho creato un repository che verrá aggiornato lezione dopo lezione con i contenuti attuali del corso. Qui potete esaminare tutti i file specificati.
Ad esempio, ho definito nelle varie cartelle DemoApp1, 2 o 3 il seguente file main.cpp:
simile a quello proposto nella prima lezione.

A questo punto, possiamo generare il nostro progetto!
Se volete risparmiarvi la fatica di creare i file singolarmente, potete utilizzare il codice da me giá scritto (preferirei che vi cimentiate voi personalmente) nel seguente modo:
mkdir -p ~/Projects
cd ~/Projects
git clone https://github.com/blackibiza/ModernCpp.git && cd ModernCpp
mkdir -p build && cd build
cmake ..
make
CMake configura quindi il progetto all'interno della cartella build/, utilizzando il file CMakeLists.txt specificato (in questo caso, la directory-padre, specificando i ".." dopo il comando cmake).
All'interno della cartella build, quindi, ci saranno i file di configurazione, tra cui i Makefile delle nostre DemoApp.
Per eseguire le applicazioni, potete eseguire i seguenti comandi:

./Applications/DemoApp1/modcpp_demoapp_one
oppure
./Applications/DemoApp2/modcpp_demoapp_two
oppure
./Applications/DemoApp3/modcpp_demoapp_three
ottenendo 3 risultati diversi:
Fig. 3 - Tre applicazioni, tre risultati diversi, un solo build step.


Oggi abbiamo appreso come organizzare quindi un progetto con CMake, organizzando al meglio le risorse (i file), divise in moduli riutilizzabili ed in applicazioni finali. Sulla base di questa struttura organizzativa andremo a costruire un esempio progettuale che dimostrerá l'utilitá finale di questa impostazione.
Come sempre, esistono differenti metodi di approcciarsi al problema e quella proposta è solo una delle tante possibili soluzioni, che personalmente utilizzo, ma ciò non vuol dire che sia la soluzione finale a tutti i mali. Ogni progetto ha le sue caratteristiche, i suoi scopi, le sue piattaforme a cui si riferisce.
Se tra i lettori ci sono altri sviluppatori con esperienza, mi piacerebbe conoscere altre possibili soluzioni da voi implementate e, perchè no, discuterne qui sul blog di Lubit.

So di iniziare ad esser noioso con questi questionari, ma sono uno degli strumenti di feedback, dopo i commenti ai singoli post, con i quali posso cercar di migliorare la qualitá dell'offerta proposta.
Qui trovate il form da compilare: il tempo a voi richiesto è sempre inferiore ai 30 secondi :)

E`stato attivato anche un gruppo di supporto su Google+: qui potete esporre le vostre domande o i vostri dubbi e cercheró di rispondervi quanto prima possibile. Su Blogger, sfortunatamente, non ci sono notifiche e-mail per i commenti, almeno non come co-autore.

Alla prossima!

9 commenti:

  1. Questo commento è stato eliminato dall'autore.

    RispondiElimina
  2. Questo commento è stato eliminato dall'autore.

    RispondiElimina
    Risposte
    1. Ciao,

      la cartella build va creata all'interno della directory di progetto, altrimenti direttamente in ~/ (home) e poi fai:

      cmake /path/directory/dove/hai/scaricato/il/progetto

      Elimina
    2. Funziona tutto perfettamente:

      http://tinypic.com/r/if9wmd/8

      Elimina
    3. Questo commento è stato eliminato dall'autore.

      Elimina
    4. Questo commento è stato eliminato dall'autore.

      Elimina
  3. Ciao , ho eliminato i miei precedenti commenti perché ora funziona tutto e i problemi erano dipesi da errori che ho commesso nella scrittura dei file cmakelists.txt (ho preferito fare tutto da me per capire meglio ciò che facevo).

    Scusa e alla prossima. :-)

    RispondiElimina
    Risposte
    1. Ciao Dario

      ah perfetto. Pensavo avessi avuto problemi con il repository.
      Regola d'oro è non utilizzare stessi nomi per "target" (progetti/eseguibili) diversi, altrimenti CMake si arrabbia!
      Alla prossima

      Elimina