A cosa serve CMake?
Secondo Wikipedia:
CMake è un [...] software per la gestione del processo di compilazione del software utilizzando un metodo indipendente dal compilatore. È progettato per supportare gerarchie di directory e applicazioni che dipendono da più librerie. Viene utilizzato in combinazione con ambienti di compilazione nativi come make, Xcode di Apple e Microsoft Visual Studio.
Con CMake, non è più necessario mantenere impostazioni separate specifiche per il compilatore / ambiente di compilazione. Hai una configurazione e funziona per molti ambienti.
CMake può generare una soluzione Microsoft Visual Studio, un progetto Eclipse o un labirinto di Makefile dagli stessi file senza modificare nulla in essi.
Dato un mucchio di directory con codice al loro interno, CMake gestisce tutte le dipendenze, gli ordini di compilazione e altre attività che il tuo progetto deve svolgere prima di poter essere compilato. In realtà NON compila nulla. Per usare CMake, devi dirgli (usando i file di configurazione chiamati CMakeLists.txt) quali eseguibili hai bisogno di compilare, a quali librerie si collegano, quali directory ci sono nel tuo progetto e cosa c'è dentro, così come tutti i dettagli come i flag o qualsiasi altra cosa di cui hai bisogno (CMake è abbastanza potente).
Se è impostato correttamente, puoi utilizzare CMake per creare tutti i file di cui il tuo "ambiente di compilazione nativo" preferito ha bisogno per svolgere il suo lavoro. In Linux, per impostazione predefinita, questo significa Makefile. Quindi, una volta eseguito CMake, creerà un gruppo di file per il proprio uso più alcuni Makefile
s. Tutto quello che devi fare in seguito è digitare "make" nella console dalla cartella principale ogni volta che hai finito di modificare il tuo codice, e bam, viene creato un eseguibile compilato e collegato.
Come funziona CMake? Che cosa fa?
Ecco un esempio di configurazione del progetto che userò in tutto:
simple/
CMakeLists.txt
src/
tutorial.cxx
CMakeLists.txt
lib/
TestLib.cxx
TestLib.h
CMakeLists.txt
build/
Il contenuto di ogni file viene mostrato e discusso in seguito.
CMake imposta il tuo progetto in base alla radice CMakeLists.txt
del tuo progetto e lo fa in qualsiasi directory tu abbia eseguito cmake
dalla console. Farlo da una cartella che non è la radice del tuo progetto produce quella che viene chiamata build out-of-source , il che significa che i file creati durante la compilazione (file obj, file lib, eseguibili, lo sai) verranno inseriti in detta cartella , tenuto separato dal codice effettivo. Aiuta a ridurre il disordine ed è preferito anche per altri motivi, che non discuterò.
Non so cosa succede se esegui cmake
su qualsiasi altro che la radice CMakeLists.txt
.
In questo esempio, dato che voglio che tutto sia posizionato all'interno della build/
cartella, devo prima navigare lì, quindi passare CMake alla directory in cui CMakeLists.txt
risiede la radice .
cd build
cmake ..
Per impostazione predefinita, questo imposta tutto utilizzando i Makefile come ho detto. Ecco come dovrebbe apparire la cartella build ora:
simple/build/
CMakeCache.txt
cmake_install.cmake
Makefile
CMakeFiles/
(...)
src/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
lib/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
Cosa sono tutti questi file? L'unica cosa di cui ti devi preoccupare è il Makefile e le cartelle del progetto .
Notare le cartelle src/
e lib/
. Questi sono stati creati perché simple/CMakeLists.txt
li punta utilizzando il comando add_subdirectory(<folder>)
. Questo comando dice a CMake di cercare in detta cartella un altro CMakeLists.txt
file ed eseguire quello script, quindi ogni sottodirectory aggiunta in questo modo deve contenere un CMakeLists.txt
file. In questo progetto, simple/src/CMakeLists.txt
descrive come costruire l'eseguibile effettivo e simple/lib/CMakeLists.txt
descrive come costruire la libreria. Ogni obiettivo che CMakeLists.txt
viene descritto verrà posizionato per impostazione predefinita nella sua sottodirectory all'interno dell'albero di compilazione. Quindi, dopo un rapido
make
nella console eseguita da build/
, vengono aggiunti alcuni file:
simple/build/
(...)
lib/
libTestLib.a
(...)
src/
Tutorial
(...)
Il progetto è costruito e l'eseguibile è pronto per essere eseguito. Cosa fai se vuoi che gli eseguibili vengano messi in una cartella specifica? Imposta la variabile CMake appropriata o modifica le proprietà di un obiettivo specifico . Maggiori informazioni sulle variabili CMake più avanti.
Come dico a CMake come creare il mio progetto?
Ecco il contenuto, spiegato, di ogni file nella directory dei sorgenti:
simple/CMakeLists.txt
:
cmake_minimum_required(VERSION 2.6)
project(Tutorial)
# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)
La versione minima richiesta dovrebbe essere sempre impostata, in base all'avviso che CMake lancia quando non lo fai. Usa qualunque sia la tua versione di CMake.
Il nome del tuo progetto può essere utilizzato in seguito e suggerisce il fatto che puoi gestire più di un progetto dagli stessi file CMake. Non mi addentrerò in questo, però.
Come accennato prima, add_subdirectory()
aggiunge una cartella al progetto, il che significa che CMake si aspetta che abbia un CMakeLists.txt
interno, che verrà quindi eseguito prima di continuare. A proposito, se ti capita di avere una funzione CMake definita puoi usarla da altri CMakeLists.txt
s nelle sottodirectory, ma devi definirla prima di usarla add_subdirectory()
o non la troverà. Tuttavia, CMake è più intelligente riguardo alle librerie, quindi questa è probabilmente l'unica volta in cui ti imbatterai in questo tipo di problema.
simple/lib/CMakeLists.txt
:
add_library(TestLib TestLib.cxx)
Per creare la tua libreria personale, dagli un nome e poi elenca tutti i file da cui è costruita. Semplice. Se avesse bisogno di un altro file foo.cxx
,, per essere compilato, dovresti invece scrivere add_library(TestLib TestLib.cxx foo.cxx)
. Questo funziona anche per i file in altre directory, ad esempio add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx)
. Maggiori informazioni sulla variabile CMAKE_SOURCE_DIR più avanti.
Un'altra cosa che puoi fare con questo è specificare che vuoi una libreria condivisa. L'esempio: add_library(TestLib SHARED TestLib.cxx)
. Non temere, è qui che CMake inizia a semplificarti la vita. Che sia condivisa o meno, ora tutto ciò che devi gestire per utilizzare una libreria creata in questo modo è il nome che le hai dato qui. Il nome di questa libreria è ora TestLib e puoi fare riferimento ad esso da qualsiasi punto del progetto. CMake lo troverà.
C'è un modo migliore per elencare le dipendenze? Sicuramente si . Controlla di seguito per ulteriori informazioni su questo.
simple/lib/TestLib.cxx
:
#include <stdio.h>
void test() {
printf("testing...\n");
}
simple/lib/TestLib.h
:
#ifndef TestLib
#define TestLib
void test();
#endif
simple/src/CMakeLists.txt
:
# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)
# Link to needed libraries
target_link_libraries(Tutorial TestLib)
# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)
Il comando add_executable()
funziona esattamente allo stesso modo add_library()
, tranne, ovviamente, genererà invece un eseguibile. È ora possibile fare riferimento a questo eseguibile come obiettivo per cose come target_link_libraries()
. Poiché tutorial.cxx utilizza il codice trovato nella libreria TestLib, lo fai notare a CMake come mostrato.
Allo stesso modo, qualsiasi file .h #incluso da qualsiasi sorgente add_executable()
che non si trova nella stessa directory della sorgente deve essere aggiunto in qualche modo. Se non fosse per il target_include_directories()
comando, lib/TestLib.h
non verrebbe trovato durante la compilazione del Tutorial, quindi l'intera lib/
cartella viene aggiunta alle directory di inclusione in cui cercare #includes. Potresti anche vedere il comando include_directories()
che agisce in modo simile, tranne per il fatto che non è necessario che tu specifichi un obiettivo poiché lo imposta completamente a livello globale, per tutti gli eseguibili. Ancora una volta, spiegherò in seguito CMAKE_SOURCE_DIR.
simple/src/tutorial.cxx
:
#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
test();
fprintf(stdout, "Main\n");
return 0;
}
Notare come è incluso il file "TestLib.h". Non c'è bisogno di includere il percorso completo: CMake si prende cura di tutto ciò che sta dietro le quinte grazie a target_include_directories()
.
Tecnicamente parlando, in un semplice albero dei sorgenti come questo puoi fare a meno della CMakeLists.txt
s sotto lib/
e src/
e aggiungere semplicemente qualcosa di simile add_executable(Tutorial src/tutorial.cxx)
a simple/CMakeLists.txt
. Dipende da te e dalle esigenze del tuo progetto.
Cos'altro dovrei sapere per utilizzare correttamente CMake?
(Argomenti AKA rilevanti per la tua comprensione)
Trovare e utilizzare i pacchetti : la risposta a questa domanda lo spiega meglio di quanto avrei mai potuto.
Dichiarazione di variabili e funzioni, utilizzo del flusso di controllo, ecc .: Dai un'occhiata a questo tutorial che spiega le basi di ciò che CMake ha da offrire, oltre ad essere una buona introduzione in generale.
Variabili CMake : ce ne sono molte, quindi quello che segue è un corso accelerato per portarti sulla strada giusta. Il wiki di CMake è un buon posto per ottenere informazioni più approfondite sulle variabili e apparentemente anche su altre cose.
Potresti voler modificare alcune variabili senza ricostruire l'albero di compilazione. Usa ccmake per questo (modifica il CMakeCache.txt
file). Ricordarsi di c
onfigure una volta terminate le modifiche e quindi g
enerare i makefile con la configurazione aggiornata.
Leggi il tutorial a cui si fa riferimento in precedenza per imparare a usare le variabili, ma per farla breve:
set(<variable name> value)
per modificare o creare una variabile.
${<variable name>}
per usarlo.
CMAKE_SOURCE_DIR
: La directory principale di source. Nell'esempio precedente, questo è sempre uguale a/simple
CMAKE_BINARY_DIR
: La directory principale della build. Nell'esempio precedente, questo è uguale a simple/build/
, ma se si esegue cmake simple/
da una cartella come foo/bar/etc/
, tutti i riferimenti a CMAKE_BINARY_DIR
in quell'albero di compilazione diventerebbero /foo/bar/etc
.
CMAKE_CURRENT_SOURCE_DIR
: La directory in cui si trova la corrente CMakeLists.txt
. Ciò significa che cambia in tutto: stampandola da simple/CMakeLists.txt
yield /simple
e stampandola da simple/src/CMakeLists.txt
yield /simple/src
.
CMAKE_CURRENT_BINARY_DIR
: Hai avuto l'idea. Questo percorso dipenderà non solo dalla cartella in cui si trova la build, ma anche dalla posizione dello CMakeLists.txt
script corrente .
Perché sono importanti? I file sorgente ovviamente non saranno nell'albero di compilazione. Se provi qualcosa di simile target_include_directories(Tutorial PUBLIC ../lib)
nell'esempio precedente, quel percorso sarà relativo all'albero di costruzione, vale a dire che sarà come scrivere ${CMAKE_BINARY_DIR}/lib
, che guarderà all'interno simple/build/lib/
. Non ci sono file .h lì; al massimo troverai libTestLib.a
. Tu ${CMAKE_SOURCE_DIR}/lib
invece vuoi .
CMAKE_CXX_FLAGS
: Flag da passare al compilatore, in questo caso il compilatore C ++. Vale anche la pena notare CMAKE_CXX_FLAGS_DEBUG
che verrà utilizzato invece se CMAKE_BUILD_TYPE
è impostato su DEBUG. Ce ne sono altri come questi; controlla il wiki di CMake .
CMAKE_RUNTIME_OUTPUT_DIRECTORY
: Indica a CMake dove mettere tutti gli eseguibili quando vengono creati. Questa è un'ambientazione globale. Ad esempio, puoi impostarlo su bin/
e posizionare tutto in modo ordinato lì. EXECUTABLE_OUTPUT_PATH
è simile, ma deprecato, nel caso in cui ci si imbatta.
CMAKE_LIBRARY_OUTPUT_DIRECTORY
: Allo stesso modo, un'impostazione globale per dire a CMake dove mettere tutti i file di libreria.
Proprietà target : puoi impostare proprietà che interessano solo un target, sia esso un eseguibile o una libreria (o un archivio ... hai capito). Ecco un buon esempio di come usarlo (con set_target_properties()
.
Esiste un modo semplice per aggiungere automaticamente sorgenti a una destinazione? Usa GLOB per elencare tutto in una data directory sotto la stessa variabile. La sintassi di esempio è FILE(GLOB <variable name> <directory>/*.cxx)
.
Potete specificare diversi tipi di build? Sì, anche se non sono sicuro di come funzioni o dei limiti di questo. Probabilmente richiede un po 'di if / then'ning, ma CMake offre un supporto di base senza configurare nulla, come i valori predefiniti per CMAKE_CXX_FLAGS_DEBUG
, ad esempio. È possibile impostare il tipo di build dall'interno del CMakeLists.txt
file tramite set(CMAKE_BUILD_TYPE <type>)
o chiamando CMake dalla console con i flag appropriati, ad esempio cmake -DCMAKE_BUILD_TYPE=Debug
.
Qualche buon esempio di progetti che utilizzano CMake? Wikipedia ha un elenco di progetti open source che utilizzano CMake, se vuoi esaminarlo. I tutorial online non sono stati altro che una delusione per me finora a questo proposito, tuttavia questa domanda di Stack Overflow ha una configurazione CMake piuttosto interessante e di facile comprensione. Vale la pena dare un'occhiata.
Utilizzo delle variabili di CMake nel codice : ecco un esempio rapido e sporco (adattato da qualche altro tutorial ):
simple/CMakeLists.txt
:
project (Tutorial)
# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)
# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)
simple/TutorialConfig.h.in
:
// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
Il file risultante generato da CMake, simple/src/TutorialConfig.h
:
// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1
Con un uso intelligente di questi puoi fare cose interessanti come spegnere una libreria e simili. Consiglio di dare un'occhiata a quel tutorial in quanto ci sono alcune cose leggermente più avanzate che sono destinate ad essere molto utili su progetti più grandi, prima o poi.
Per tutto il resto, Stack Overflow è pieno di domande specifiche e risposte concise, il che è ottimo per tutti tranne che per chi non lo sapesse.