Come spiegato nel mio post precedente, ogni progetto basato su CMake deve contenere uno script chiamato CMakeLists.txt. Questo script definisce i target, ma può anche fare molte altre cose, come trovare librerie di terze parti o generare file header C++. Gli script di CMake hanno molta flessibilità.

Ogni volta che si integra una libreria esterna, e spesso quando si aggiunge il supporto per un’altra piattaforma, è necessario modificare lo script. Ho passato molto tempo a modificare gli script di CMake senza capire veramente il linguaggio, dato che la documentazione è piuttosto dispersiva, ma alla fine, le cose sono scattate. L’obiettivo di questo post è di portarvi allo stesso punto il più velocemente possibile.

Questo post non coprirà tutti i comandi incorporati in CMake, poiché ce ne sono centinaia, ma è una guida abbastanza completa alla sintassi e al modello di programmazione del linguaggio.

Hello World

Se crei un file chiamato hello.txt con il seguente contenuto:

message("Hello world!") # A message to print

…puoi eseguirlo dalla linea di comando usando cmake -P hello.txt. (L’opzione -P esegue lo script dato, ma non genera una pipeline di compilazione). Come previsto, stampa “Hello world!”.

$ cmake -P hello.txtHello world!

All Variables Are Strings

In CMake, ogni variabile è una stringa. Puoi sostituire una variabile dentro un letterale di stringa circondandola con ${}. Questo è chiamato un riferimento di variabile. Modifica hello.txt come segue:

message("Hello ${NAME}!") # Substitute a variable into the message

Ora, se definiamo NAME sulla linea di comando cmake usando l’opzione -D, lo script la userà:

$ cmake -DNAME=Newman -P hello.txtHello Newman!

Quando una variabile è indefinita, è di default una stringa vuota:

$ cmake -P hello.txtHello !

Per definire una variabile dentro uno script, usa il comando set. Il primo argomento è il nome della variabile da assegnare, e il secondo argomento è il suo valore:

set(THING "funk")message("We want the ${THING}!")

Le virgolette intorno agli argomenti sono opzionali, finché non ci sono spazi o riferimenti a variabili nell’argomento. Per esempio, avrei potuto scrivere set("THING" funk) nella prima riga sopra – sarebbe stato equivalente. Per la maggior parte dei comandi CMake (eccetto if e while, descritti più avanti), la scelta di citare o meno tali argomenti è semplicemente una questione di stile. Quando l’argomento è il nome di una variabile, tendo a non usare le virgolette.

Puoi simulare una struttura dati usando i prefissi

CMake non ha classi, ma puoi simulare una struttura dati definendo un gruppo di variabili con nomi che iniziano con lo stesso prefisso. Puoi poi cercare le variabili in quel gruppo usando riferimenti annidati a variabili ${}. Per esempio, il seguente script stamperà “John Smith vive al 123 Fake St.”:

set(JOHN_NAME "John Smith")set(JOHN_ADDRESS "123 Fake St")set(PERSON "JOHN")message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")

Si possono anche usare riferimenti di variabili nel nome della variabile da impostare. Per esempio, se il valore di PERSON è ancora “JOHN”, il seguente imposterà la variabile JOHN_NAME a “John Goodman”:

set(${PERSON}_NAME "John Goodman")

Every Statement is a Command

In CMake, ogni statement è un comando che prende una lista di argomenti stringa e non ha valore di ritorno. Gli argomenti sono separati da spazi (non quotati). Come abbiamo già visto, il comando set definisce una variabile allo scopo del file.

Come altro esempio, CMake ha un comando math che esegue l’aritmetica. Il primo argomento deve essere EXPR, il secondo argomento è il nome della variabile da assegnare, e il terzo argomento è l’espressione da valutare – tutte stringhe. Nota che nella terza linea sotto, CMake sostituisce il valore della stringa MY_SUM nell’argomento che lo racchiude prima di passare l’argomento a math.

math(EXPR MY_SUM "1 + 1") # Evaluate 1 + 1; store result in MY_SUMmessage("The sum is ${MY_SUM}.")math(EXPR DOUBLE_SUM "${MY_SUM} * 2") # Multiply by 2; store result in DOUBLE_SUMmessage("Double that is ${DOUBLE_SUM}.")

C’è un comando CMake per qualsiasi cosa tu debba fare. Il comando string ti permette di eseguire una manipolazione avanzata delle stringhe, inclusa la sostituzione delle espressioni regolari. Il comando file può leggere o scrivere file, o manipolare i percorsi del filesystem.

Comandi di controllo del flusso

Anche le istruzioni di controllo del flusso sono comandi. I comandi if/endif eseguono i comandi allegati in modo condizionale. Gli spazi vuoti non hanno importanza, ma è comune indentare i comandi allegati per la leggibilità. Il seguente controlla se la variabile incorporata di CMake WIN32 è impostata:

if(WIN32) message("You're running CMake on Windows.")endif()

CMake ha anche i comandi while/endwhile che, come ci si potrebbe aspettare, ripetono i comandi allegati finché la condizione è vera. Ecco un ciclo che stampa tutti i numeri di Fibonacci fino a un milione:

set(A "1")set(B "1")while(A LESS "1000000") message("${A}") # Print A math(EXPR T "${A} + ${B}") # Add the numeric values of A and B; store result in T set(A "${B}") # Assign the value of B to A set(B "${T}") # Assign the value of T to Bendwhile()

Le condizioni if e while di MCake non sono scritte come in altri linguaggi. Per esempio, per eseguire un confronto numerico, devi specificare LESS come argomento di stringa, come mostrato sopra. La documentazione spiega come scrivere una condizione valida.

if e while sono diversi dagli altri comandi di CMake in quanto se il nome di una variabile è specificato senza virgolette, il comando userà il valore della variabile. Nel codice sopra, ho approfittato di questo comportamento scrivendo while(A LESS "1000000") invece di while("${A}" LESS "1000000") – entrambe le forme sono equivalenti. Altri comandi di CMake non lo fanno.

Le liste sono solo stringhe delimitate da un punto e virgola

CMake ha una speciale regola di sostituzione per gli argomenti senza apici. Se l’intero argomento è un riferimento a una variabile senza virgolette, e il valore della variabile contiene punti e virgola, CMake dividerà il valore in corrispondenza dei punti e virgola e passerà più argomenti al comando che lo racchiude. Per esempio, il seguente passa tre argomenti a math:

set(ARGS "EXPR;T;1 + 1")math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")

D’altra parte, gli argomenti quotati non sono mai divisi in argomenti multipli, anche dopo la sostituzione. CMake passa sempre una stringa quotata come un singolo argomento, lasciando intatto il punto e virgola:

set(ARGS "EXPR;T;1 + 1")message("${ARGS}") # Prints: EXPR;T;1 + 1

Se più di due argomenti sono passati al comando set, sono uniti da punto e virgola, quindi assegnati alla variabile specificata. Questo crea effettivamente una lista dagli argomenti:

set(MY_LIST These are separate arguments)message("${MY_LIST}") # Prints: These;are;separate;arguments

È possibile manipolare tali liste usando il comando list:

set(MY_LIST These are separate arguments)list(REMOVE_ITEM MY_LIST "separate") # Removes "separate" from the listmessage("${MY_LIST}") # Prints: These;are;arguments

Il comando foreach/endforeach accetta più argomenti. Itera su tutti gli argomenti tranne il primo, assegnando ciascuno alla variabile nominata:

foreach(ARG These are separate arguments) message("${ARG}") # Prints each word on a separate lineendforeach()

Puoi iterare su una lista passando un riferimento di variabile non quotato a foreach. Come con qualsiasi altro comando, CMake dividerà il valore della variabile e passerà più argomenti al comando:

foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments message("${ARG}") # Prints each item on a separate lineendforeach()

Functions Run In Their Own Scope; Macros Don’t

In CMake, puoi usare una coppia di comandi function/endfunction per definire una funzione. Eccone una che raddoppia il valore numerico del suo argomento, poi stampa il risultato:

function(doubleIt VALUE) math(EXPR RESULT "${VALUE} * 2") message("${RESULT}")endfunction()doubleIt("4") # Prints: 8

Le funzioni funzionano nel loro ambito. Nessuna delle variabili definite in una funzione inquina l’ambito del chiamante. Se volete restituire un valore, potete passare il nome di una variabile alla vostra funzione, poi chiamate il comando set con l’argomento speciale PARENT_SCOPE:

function(doubleIt VARNAME VALUE) math(EXPR RESULT "${VALUE} * 2") set(${VARNAME} "${RESULT}" PARENT_SCOPE) # Set the named variable in caller's scopeendfunction()doubleIt(RESULT "4") # Tell the function to set the variable named RESULTmessage("${RESULT}") # Prints: 8

Similmente, una coppia di comandi macro/endmacro definisce una macro. A differenza delle funzioni, le macro funzionano nello stesso ambito del loro chiamante. Pertanto, tutte le variabili definite all’interno di una macro sono impostate nell’ambito del chiamante. Possiamo sostituire la funzione precedente con la seguente:

macro(doubleIt VARNAME VALUE) math(EXPR ${VARNAME} "${VALUE} * 2") # Set the named variable in caller's scopeendmacro()doubleIt(RESULT "4") # Tell the macro to set the variable named RESULTmessage("${RESULT}") # Prints: 8

Sia le funzioni che le macro accettano un numero arbitrario di argomenti. Gli argomenti senza nome sono esposti alla funzione come una lista, attraverso una variabile speciale chiamata ARGN. Ecco una funzione che raddoppia ogni argomento che riceve, stampando ognuno su una linea separata:

function(doubleEach) foreach(ARG ${ARGN}) # Iterate over each argument math(EXPR N "${ARG} * 2") # Double ARG's numeric value; store result in N message("${N}") # Print N endforeach()endfunction()doubleEach(5 6 7 8) # Prints 10, 12, 14, 16 on separate lines

Includendo altri script

Le variabili di CMake sono definite allo scopo del file. Il comando include esegue un altro script CMake nello stesso scope dello script chiamante. E’ molto simile alla direttiva #include in C/C++. E’ tipicamente usata per definire un insieme comune di funzioni o macro nello script chiamante. Usa la variabile CMAKE_MODULE_PATH come percorso di ricerca.

Il comando find_package cerca script della forma Find*.cmake e li esegue nello stesso ambito. Tali script sono spesso usati per aiutare a trovare librerie esterne. Per esempio, se c’è un file chiamato FindSDL2.cmake nel percorso di ricerca, find_package(SDL2) è equivalente a include(FindSDL2.cmake). (Nota che ci sono diversi modi di usare il comando find_package – questo è solo uno di essi.)

Il comando add_subdirectory di CMake, d’altra parte, crea un nuovo scope, poi esegue lo script chiamato CMakeLists.txt dalla directory specificata in quel nuovo scope. Tipicamente lo usi per aggiungere un altro sottoprogetto basato su CMake, come una libreria o un eseguibile, al progetto chiamante. Gli obiettivi definiti dal sottoprogetto sono aggiunti alla pipeline di compilazione a meno che non sia specificato diversamente. Nessuna delle variabili definite nello script del sottoprogetto inquinerà l’ambito del genitore a meno che non venga usata l’opzione PARENT_SCOPE del comando set.

Come esempio, ecco alcuni degli script coinvolti quando esegui CMake sul progetto Turf:

Immissione e impostazione delle proprietà

Uno script CMake definisce i target usando i comandi add_executable, add_library o add_custom_target. Una volta che un target è creato, ha delle proprietà che puoi manipolare usando i comandi get_property e set_property. A differenza delle variabili, i target sono visibili in ogni ambito, anche se sono stati definiti in una sottodirectory. Tutte le proprietà dei target sono stringhe.

add_executable(MyApp "main.cpp") # Create a target named MyApp# Get the target's SOURCES property and assign it to MYAPP_SOURCESget_property(MYAPP_SOURCES TARGET MyApp PROPERTY SOURCES)message("${MYAPP_SOURCES}") # Prints: main.cpp

Altre proprietà dei target includono LINK_LIBRARIES, INCLUDE_DIRECTORIES e COMPILE_DEFINITIONS. Queste proprietà sono modificate, indirettamente, dai comandi target_link_libraries, target_include_directories e target_compile_definitions. Alla fine dello script, CMake usa queste proprietà di destinazione per generare la pipeline di compilazione.

Ci sono anche proprietà per altre entità CMake. C’è un insieme di proprietà di directory in ogni ambito di file. C’è un insieme di proprietà globali che è accessibile da tutti gli script. E c’è un insieme di proprietà di file sorgente per ogni file sorgente C/C++.

.

Articles

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.