Gestire la costruzione del software con CMake
Esistono innumerevoli strumenti per gestire la costruzione del software ossia quel processo produttivo che parte dai sorgenti e sfocia nella creazione degli artefatti, cioè i programmi compilati e pronti all’uso. CMake è uno di questi.
CMake (Cross Platform Make) è un software (libero) multipiattaforma nato per semplificare la creazione automatica dei Makefile.
In sostanza CMake include un suo semplicissimo linguaggio che permette di definire in modo estremamente compatto cosa si deve fare per completare azioni come compilare dei sorgenti e installare o rimuovere i binari prodotti. Poi sarà lui a creare i Makefile, adeguandoli al sistema operativo sottostante.
CMake gestisce la costruzione di software direttamente nel punto dove risiede il codice o – meglio – all’esterno in altre directory: in questo modo si evita di mischiare codice e binari con tutte le conseguenze del caso. È possibile usarlo per creare più compilazioni distinte in un colpo solo (ad esempio la versione base e quella ottimizzata di uno stesso programma) e anche per invocare compilatori per costruire programmi per altre piattaforme (cross-compiling).
Infine CMake crea e gestisce una sua apposita cache in cui inserisce tutto quello che trova e produce, in modo da velocizzare le operazioni ed ottenere Makefile personalizzati per il contesto.
Un passo indietro
Chi ha lavorato con gli Autotools di GNU sa bene quanto possa essere noioso sfruttarli per poter ricompilare lo stesso codice su più piattaforme diverse, soprattutto per il discorso del linguaggio adottato, l’m4, qualcosa di assurdamente complicato per delle azioni tutto sommato banali: casi particolari a parte, cosa ci può essere di tanto complicato nell’indicare al compilatore dove trovare file sorgenti, file header e librerie necessari a costruire un software?
In CMake tutto quello che impiega più file con gli Autotools si riduce a creare un semplice file di testo, CMakeLists.txt, che conterrà poche direttive utili a far capire a CMake cosa deve fare. Ad esempio, supponendo di avere un file sorgente main.cppsenza dipendenze esterne e volendo creare un eseguibile chiamato miosw basta creare il file CMakeLists.txt con queste due sole righe:
PROJECT(miosw)
ADD_EXECUTABLE(${PROJECT_NAME} main.cpp)
Eseguendo il comando cmake . si otterrà un Makefile/file di progetto adatto alla piattaforma corrente; lanciando poi il comando make o chi per esso – CMake supporta make, XCode, Visual Studio e altro ancora – si otterrà infine il binario corretto.
In altre parole dalle due righe sopra-citate si arriverà infine all’eseguibile miosw.exesu Windows, mentre su Linux si otterrà il binario eseguibile miosw.
Il formato per scrivere i file CMakeLists.txt è semplice e chiaro: comandi e parametri. I parametri possono essere resi sotto forma di variabili (riconoscibili dal simbolo del dollaro $). In sostanza CMake include un linguaggio di programmazione, estremamente piccolo e molto intuitivo.
Ottenere una libreria è altrettanto semplice, basta cambiare un paio di parole:
PROJECT(miosw)
ADD_LIBRARY(${PROJECT_NAME} SHARED main.cpp)
Il risultato finale sarà una libreria .dll su Windows ed una .so su Linux.
Un esempio più serio
Supponendo di dover compilare ed installare la libreria C++ mialib costituita da più file sorgenti e da linkare a librerie esterne o di sistema. Un esempio di file CMakeLists.txt potrebbe assomigliare al seguente:
project(mialib)
cmake_minimum_required(VERSION 2.8)
# Scommentare se si vuole seguire passo-passo tutti
# i comandi eseguiti da make
#set(CMAKE_VERBOSE_MAKEFILE 1)# Qualche parametro addizionale per GCC
set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -std=c++0x”)
# Percorsi degli header file (equivalenti a -I su GCC)
include_directories(
“${CMAKE_CURRENT_SOURCE_DIR}/include/mialib”
“${CMAKE_CURRENT_SOURCE_DIR}/include/mialib/client”
“${CMAKE_CURRENT_SOURCE_DIR}/include/mialib/server”
)# Percorsi dei file sorgenti: CMake cerca da solo i file sorgenti
# e ne inserisce i percorsi in variabili. Ad esempio SRC_LIST
# conterrà tutti i file sorgenti trovati nella directory src.
aux_source_directory(src SRC_LIST)
aux_source_directory(src/client CLT_SRC_LIST)
aux_source_directory(src/server SRV_SRC_LIST)
# Crea la libreria condivisa (.dll/.so) passandogli i parametri
add_library(${PROJECT_NAME} SHARED ${SRC_LIST} ${CLT_SRC_LIST} ${SRV_SRC_LIST})# Parametri per indicare i percorsi sotto cui cercare le librerie
# (equivalente a -L su GCC)
link_directories(/usr /usr/local)
# Librerie esterne/di sistema da aggiungere in fase di link
# (equivalente a -l su GCC)
target_link_libraries(${PROJECT_NAME} syslib1 syslib2)
# Azioni da effettuare per l’installazione (es: make install):
# installazione dei file header (prima riga) e della libreria
# compilata (seconda riga).
# Si noti come per la seconda riga si dica a CMake dove installare i
# singoli artefatti sulla base del loro tipo: i programmi in bin, le
# librerie condivise e quelle statiche in lib.
# I percorsi relativi bin e lib si riferiscono ai percorsi di default,
# aventi prefisso /usr/local su Linux. Da linea di comando si possono
# comunque ridefinire.
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/mialib DESTINATION include)
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)