Salta al contenuto
Note per Studenti Esercizio - Ristrutturare un progetto con CMake (laboratorio 9)

Esercizio - Ristrutturare un progetto con CMake (laboratorio 9)

Esame
In questa pagina 6

Testo. Considerate il progetto InertialDriver sviluppato per la prova intermedia. Ristrutturatelo in modo che sia compilabile tramite CMake. La struttura del progetto deve essere:

InertialDriver
├── include
│   └── *.h
├── CMakeLists.txt
├── src
│   ├── *.cpp
│   └── *.hpp
└── build

Dovete creare con CMake: la libreria libmyvector; la libreria libinertialdriver (che utilizza libmyvector); l'eseguibile main che utilizza libinertialdriver.

Teoria: CMake - build di progetti C++Un progetto C++ non è fatto solo di sorgenti: bisogna dire quali file formano ogni eseguibile e ogni libreria (target), quali librerie linkare, dove stanno gli header e con quale standard compilare. CMake è un meta-sistema di build indipendente da compilatore e piattaforma: si descrive il progetto in un file di testo CMakeLists.txt (cmake_minimum_required, project, add_library, add_executable, target_link_libraries, include directories) e CMake genera i file del sistema di build nativo (Makefile, progetto di IDE). Lato utente si fa una configurazione (mkdir build, cd build, cmake ..) e poi la compilazione (make o cmake --build .) in una cartella di build separata dai sorgenti; le librerie di soli template (header-only) si dichiarano INTERFACE perché non producono codice precompilato.CMake - build di progetti C++ →. Il progetto di partenza è quello di Esercizio - InertialDriver con buffer circolare (prova intermedia 2025-26).


1. Quali file vanno dove

La soluzione della prova intermedia ha già la struttura richiesta. Ogni file del progetto trova il suo posto così:

file cartella perché
myvector.h include/ header pubblico della classe template
myvector.hpp src/ definizioni dei membri del template (incluso da myvector.h)
inertial_driver.h include/ interfaccia di InertialDriver
inertial_driver.cpp src/ implementazione (l'unico da compilare in una libreria)
main.cpp src/ il main di test
test_errori.cpp src/ secondo eseguibile di prova, con i casi di eccezione
CMakeLists.txt radice uno solo, nella cartella principale
build/ radice cartella di compilazione (vuota nella consegna)

L'#include "myvector.hpp" in fondo a myvector.h funziona perché la cartella src/ è tra le directory di include del target myvector.

2. Il problema di libmyvector

Il testo chiede "la libreria libmyvector". Ma la MyVector della prova è un template: le sue funzioni sono istanziate nel file che la usa, e non esiste nessun .cpp da compilare in una libreria. Per CMake (CMake - build di progetti C++Un progetto C++ non è fatto solo di sorgenti: bisogna dire quali file formano ogni eseguibile e ogni libreria (target), quali librerie linkare, dove stanno gli header e con quale standard compilare. CMake è un meta-sistema di build indipendente da compilatore e piattaforma: si descrive il progetto in un file di testo CMakeLists.txt (cmake_minimum_required, project, add_library, add_executable, target_link_libraries, include directories) e CMake genera i file del sistema di build nativo (Makefile, progetto di IDE). Lato utente si fa una configurazione (mkdir build, cd build, cmake ..) e poi la compilazione (make o cmake --build .) in una cartella di build separata dai sorgenti; le librerie di soli template (header-only) si dichiarano INTERFACE perché non producono codice precompilato.CMake - build di progetti C++ →) una libreria di soli header si dichiara INTERFACE: non produce nessun file libmyvector.a, ma porta con sé "le cartelle da aggiungere agli include di chi la usa". Nel progetto libmyvector è quindi un target logico. Se invece si fosse scelta una MyVector senza template (permesso dalla prova), esisterebbe un myvector.cpp e la libreria sarebbe vera:

cmake
# variante senza template
add_library(myvector src/myvector.cpp)
target_include_directories(myvector PUBLIC include)

3. Il CMakeLists.txt

CMakeLists.txtcmake
cmake_minimum_required(VERSION 3.10)
project(InertialDriver LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 1) libmyvector: soli template, quindi libreria INTERFACE (nessun file da compilare)
add_library(myvector INTERFACE)
target_include_directories(myvector INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)

# 2) libinertialdriver: usa libmyvector
add_library(inertialdriver src/inertial_driver.cpp)
target_include_directories(inertialdriver PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
target_link_libraries(inertialdriver PUBLIC myvector)
target_compile_options(inertialdriver PRIVATE -Wall -Wextra -O2)

# 3) eseguibili: main usa libinertialdriver
add_executable(main src/main.cpp)
target_link_libraries(main PRIVATE inertialdriver)
target_compile_options(main PRIVATE -Wall -Wextra -O2)

add_executable(test_errori src/test_errori.cpp)
target_link_libraries(test_errori PRIVATE inertialdriver)

Riga per riga:

  • cmake_minimum_required, project, CMAKE_CXX_STANDARD: versione di CMake, nome, standard del linguaggio. Il progetto non usa nient'altro che la libreria standard, quindi non c'è nessun find_package.
  • add_library(myvector INTERFACE) + target_include_directories(... INTERFACE ...): la libreria di soli header; le due cartelle sono quelle che chi la usa deve avere tra gli include (include per myvector.h, src per myvector.hpp).
  • add_library(inertialdriver src/inertial_driver.cpp): una libreria vera, a partire dall'unico .cpp (statica, perché non è indicato SHARED). target_include_directories(... PUBLIC include) dà l'header inertial_driver.h alla libreria e a chi la usa.
  • target_link_libraries(inertialdriver PUBLIC myvector): inertialdriver dipende da myvector. PUBLIC perché inertial_driver.h include myvector.h nella sua interfaccia pubblica (il membro MyVector<Measure> buffer_): anche chi usa inertialdriver deve vedere le directory di myvector. CMake le propaga a main da solo.
  • add_executable(main src/main.cpp) + target_link_libraries(main PRIVATE inertialdriver): l'eseguibile main si linka alla libreria; PRIVATE perché nessuno linka main.
  • -Wall -Wextra -O2: gli avvisi richiesti dalla correzione e le ottimizzazioni (nel progetto finale del corso -O2 è obbligatorio).

4. Uso: configurazione e compilazione

InertialDriver$ mkdir build
InertialDriver$ cd build
InertialDriver/build$ cmake ..          # configurazione
InertialDriver/build$ make              # compilazione (o: cmake --build .)
InertialDriver/build$ ./main

La cartella build contiene alla fine libinertialdriver.a, gli eseguibili main e test_errori, e tutti i file intermedi di CMake. Non va consegnata: la consegna contiene sorgenti, CMakeLists.txt e README.txt, e il progetto viene compilato dal correttore con gli stessi tre comandi.

5. Come è stato verificato

cmake non è installato nell'ambiente di prova, quindi il CMakeLists.txt non è stato eseguito: segue la documentazione ufficiale e il formato delle slide. Si è invece verificato, con il compilatore, che la stessa sequenza di operazioni che CMake genera funziona sulla struttura di cartelle indicata:

cosa fa CMake comando equivalente (dalla cartella InertialDriver)
add_library(inertialdriver src/inertial_driver.cpp) c++ -std=c++17 -O2 -Iinclude -Isrc -c src/inertial_driver.cpp -o build/inertial_driver.o
(archivio della libreria statica) ar rcs build/libinertialdriver.a build/inertial_driver.o
add_executable(main src/main.cpp) + target_link_libraries c++ -std=c++17 -O2 -Iinclude -Isrc src/main.cpp -Lbuild -linertialdriver -o build/main

I tre comandi, con zig c++ per wasm32-wasi al posto di c++ (e con le throw sostituite da abort per poterli eseguire), hanno prodotto libinertialdriver.a e un main che stampa le stesse righe OK ... del test della prova intermedia. Il test_errori è stato compilato per Linux con -Wall -Wextra senza avvisi.

6. Errori da evitare

  • add_library(myvector SHARED) (o STATIC) senza sorgenti: CMake si ferma con "no SOURCES given". Per i template: INTERFACE.
  • Dimenticare myvector in target_link_libraries(inertialdriver ...): inertial_driver.h non trova myvector.h al momento della compilazione di main.
  • Includere in add_library(inertialdriver ...) anche main.cpp: due main quando si linka.
  • Lasciare find_package(Qt5 ...) del file d'esempio: la configurazione fallisce dove Qt non c'è.
  • Consegnare la cartella build o gli eseguibili.
  • Più di un CMakeLists.txt (il progetto finale ne vuole uno solo).

Versione ripasso

CMakeLists.txtcmake
cmake_minimum_required(VERSION 3.10)
project(InertialDriver LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(myvector INTERFACE)
target_include_directories(myvector INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)

add_library(inertialdriver src/inertial_driver.cpp)
target_include_directories(inertialdriver PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
target_link_libraries(inertialdriver PUBLIC myvector)
target_compile_options(inertialdriver PRIVATE -Wall -Wextra -O2)

add_executable(main src/main.cpp)
target_link_libraries(main PRIVATE inertialdriver)
target_compile_options(main PRIVATE -Wall -Wextra -O2)
  • PUBLIC su myvector perché inertial_driver.h include myvector.h: chi usa inertialdriver deve vedere le directory di myvector (CMake le propaga a main). PRIVATE per main. Nessun find_package: solo libreria standard.
  • Uso: mkdir build, cd build, cmake .., make, ./main. Non si consegna build.
  • Verifica: cmake non era installato; verificata la sequenza equivalente: c++ -std=c++17 -O2 -Iinclude -Isrc -c src/inertial_driver.cpp -o build/inertial_driver.o, ar rcs build/libinertialdriver.a build/inertial_driver.o, c++ ... src/main.cpp -Lbuild -linertialdriver -o build/main.
  • Errori tipici: libreria di template STATIC/SHARED, myvector non linkato a inertialdriver, main.cpp dentro la libreria, find_package inutile, più CMakeLists.txt.

Esercizi su questo argomento

Teoria collegata