Esercizio - Ristrutturare un progetto con CMake (laboratorio 9)
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
└── buildDovete 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:
# variante senza template
add_library(myvector src/myvector.cpp)
target_include_directories(myvector PUBLIC include)3. Il CMakeLists.txt
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'è nessunfind_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 (includepermyvector.h,srcpermyvector.hpp).add_library(inertialdriver src/inertial_driver.cpp): una libreria vera, a partire dall'unico.cpp(statica, perché non è indicatoSHARED).target_include_directories(... PUBLIC include)dà l'headerinertial_driver.halla libreria e a chi la usa.target_link_libraries(inertialdriver PUBLIC myvector):inertialdriverdipende damyvector.PUBLICperchéinertial_driver.hincludemyvector.hnella sua interfaccia pubblica (il membroMyVector<Measure> buffer_): anche chi usainertialdriverdeve vedere le directory dimyvector. CMake le propaga amainda solo.add_executable(main src/main.cpp)+target_link_libraries(main PRIVATE inertialdriver): l'eseguibilemainsi linka alla libreria;PRIVATEperché nessuno linkamain.-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$ ./mainLa 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)(oSTATIC) senza sorgenti: CMake si ferma con "no SOURCES given". Per i template:INTERFACE.- Dimenticare
myvectorintarget_link_libraries(inertialdriver ...):inertial_driver.hnon trovamyvector.hal momento della compilazione dimain. - Includere in
add_library(inertialdriver ...)anchemain.cpp: duemainquando si linka. - Lasciare
find_package(Qt5 ...)del file d'esempio: la configurazione fallisce dove Qt non c'è. - Consegnare la cartella
buildo gli eseguibili. - Più di un
CMakeLists.txt(il progetto finale ne vuole uno solo).
Versione ripasso
- Testo: ristrutturare
InertialDriver(prova intermedia) con CMake: cartelleinclude/*.h,src/*.cppe*.hpp,CMakeLists.txtin radice,build/. Crearelibmyvector,libinertialdriver(usalibmyvector), eseguibilemain(usalibinertialdriver). 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++ →; progetto: Esercizio - InertialDriver con buffer circolare (prova intermedia 2025-26). libmyvector:MyVectorè un template, nessun.cppda compilare, quindiadd_library(myvector INTERFACE)contarget_include_directories(myvector INTERFACE $<BUILD_INTERFACE:.../include> $<BUILD_INTERFACE:.../src>)(.hppinsrc). ConMyVectorsenza template:add_library(myvector src/myvector.cpp).- File:
myvector.h,inertial_driver.hininclude/;myvector.hpp,inertial_driver.cpp,main.cpp,test_errori.cppinsrc/.
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)PUBLICsumyvectorperchéinertial_driver.hincludemyvector.h: chi usainertialdriverdeve vedere le directory dimyvector(CMake le propaga amain).PRIVATEpermain. Nessunfind_package: solo libreria standard.- Uso:
mkdir build,cd build,cmake ..,make,./main. Non si consegnabuild. - Verifica:
cmakenon 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,myvectornon linkato ainertialdriver,main.cppdentro la libreria,find_packageinutile, piùCMakeLists.txt.