Zoals uitgelegd in mijn vorige post, moet ieder CMake-gebaseerd project een script bevatten met de naam CMakeLists.txt
. Dit script definieert targets, maar het kan ook een heleboel andere dingen doen, zoals het vinden van bibliotheken van derden of het genereren van C++ header bestanden. CMake scripts hebben veel flexibiliteit.
Iedere keer dat u een externe bibliotheek integreert, en vaak bij het toevoegen van ondersteuning voor een ander platform, zult u het script moeten bewerken. Ik heb lange tijd CMake-scripts bewerkt zonder de taal echt te begrijpen, aangezien de documentatie nogal versnipperd is, maar uiteindelijk klikte het. Het doel van deze post is om je zo snel mogelijk op hetzelfde punt te brengen.
Deze post zal niet alle ingebouwde commando’s van CMake behandelen, omdat er honderden zijn, maar het is een vrij complete gids voor de syntaxis en het programmeermodel van de taal.
Hello World
Als u een bestand maakt met de naam hello.txt
met de volgende inhoud:
message("Hello world!") # A message to print
…kunt u het vanaf de commandoregel uitvoeren met cmake -P hello.txt
. (De optie -P
voert het gegeven script uit, maar genereert geen bouwpijplijn). Zoals verwacht, drukt het af “Hallo wereld!”.
$ cmake -P hello.txtHello world!
Alle variabelen zijn strings
In CMake, is iedere variabele een string. U kunt een variabele in een string literal plaatsen door deze te omringen met ${}
. Dit wordt een variabele verwijzing genoemd. Wijzig hello.txt
als volgt:
message("Hello ${NAME}!") # Substitute a variable into the message
Nu, als we NAME
definiëren op de cmake
commandolijn met de -D
optie, zal het script het gebruiken:
$ cmake -DNAME=Newman -P hello.txtHello Newman!
Wanneer een variabele ongedefinieerd is, wordt deze standaard gedefinieerd als een lege string:
$ cmake -P hello.txtHello !
Om een variabele binnen een script te definiëren, gebruikt u het set
commando. Het eerste argument is de naam van de variabele die moet worden toegewezen, en het tweede argument is de waarde ervan:
set(THING "funk")message("We want the ${THING}!")
Quotes rond argumenten zijn optioneel, zolang er geen spaties of variabele verwijzingen in het argument staan. Ik had bijvoorbeeld set("THING" funk)
kunnen schrijven in de eerste regel hierboven – dat zou gelijkwaardig zijn geweest. Voor de meeste CMake commando’s (behalve if
en while
, hieronder beschreven), is de keuze om zulke argumenten al dan niet te quoten gewoon een kwestie van stijl. Als het argument de naam van een variabele is, gebruik ik geen aanhalingstekens.
U kunt een datastructuur simuleren met behulp van voorvoegsels
CMake kent geen klassen, maar u kunt een datastructuur simuleren door een groep variabelen te definiëren met namen die beginnen met hetzelfde voorvoegsel. Je kunt dan variabelen in die groep opzoeken met geneste ${}
variabele verwijzingen. Bijvoorbeeld, het volgende script zal “John Smith woont op 123 Fake St.” afdrukken:
set(JOHN_NAME "John Smith")set(JOHN_ADDRESS "123 Fake St")set(PERSON "JOHN")message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")
U kunt zelfs variabele verwijzingen gebruiken in de naam van de variabele die u wilt instellen. Bijvoorbeeld, als de waarde van PERSON
nog steeds “JOHN” is, zal het volgende de variabele JOHN_NAME
instellen op “John Goodman”:
set(${PERSON}_NAME "John Goodman")
Elk statement is een commando
In CMake is elk statement een commando dat een lijst van string-argumenten neemt en geen terugkeerwaarde heeft. Argumenten worden gescheiden door spaties (zonder aanhalingstekens). Zoals we al gezien hebben, definieert het set
commando een variabele op bestandsniveau.
Als een ander voorbeeld, CMake heeft een math
commando dat rekenkundige bewerkingen uitvoert. Het eerste argument moet EXPR
zijn, het tweede argument is de naam van de variabele die moet worden toegewezen, en het derde argument is de uitdrukking die moet worden geëvalueerd – allemaal strings. Merk op dat op de derde regel hieronder, CMake de string waarde van MY_SUM
substitueert in het omringende argument alvorens het argument door te geven aan 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}.")
Er is een CMake commando voor zo ongeveer alles wat u nodig hebt om te doen. Met het string
commando kunt u geavanceerde string manipulatie uitvoeren, inclusief reguliere expressie vervanging. Met het file
commando kunt u bestanden lezen of schrijven, of paden in het bestandssysteem manipuleren.
Flow Control Commando’s
Zelfs flow control statements zijn commando’s. De if
/endif
commando’s voeren de ingesloten commando’s voorwaardelijk uit. Whitespace doet er niet toe, maar het is gebruikelijk om de ingesloten commando’s te laten inspringen voor de leesbaarheid. Het volgende controleert of CMake’s ingebouwde variabele WIN32
is ingesteld:
if(WIN32) message("You're running CMake on Windows.")endif()
CMake heeft ook while
/endwhile
commando’s die, zoals je zou verwachten, de ingesloten commando’s herhalen zolang de voorwaarde waar is. Hier is een lus die alle Fibonacci getallen tot een miljoen afdrukt:
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()
CMake’s if
en while
voorwaarden worden niet op dezelfde manier geschreven als in andere talen. Om bijvoorbeeld een numerieke vergelijking uit te voeren, moet u LESS
specificeren als een string-argument, zoals hierboven getoond. De documentatie legt uit hoe je een geldige conditie schrijft.
if
en while
zijn anders dan andere CMake commando’s in die zin dat als de naam van een variabele wordt opgegeven zonder aanhalingstekens, het commando de waarde van de variabele zal gebruiken. In de bovenstaande code heb ik gebruik gemaakt van dat gedrag door while(A LESS "1000000")
te schrijven in plaats van while("${A}" LESS "1000000")
– beide vormen zijn equivalent. Andere CMake commando’s doen dat niet.
Lijsten zijn gewoon door puntkomma’s gescheiden tekenreeksen
CMake heeft een speciale substitutieregel voor onaangehaalde argumenten. Als het hele argument een variabele verwijzing is zonder aanhalingstekens, en de waarde van de variabele bevat puntkomma’s, dan zal CMake de waarde splitsen bij de puntkomma’s en meerdere argumenten doorgeven aan het omringende commando. Bijvoorbeeld, het volgende geeft drie argumenten door aan math
:
set(ARGS "EXPR;T;1 + 1")math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")
Aan de andere kant, worden geciteerde argumenten nooit gesplitst in meerdere argumenten, zelfs niet na substitutie. CMake geeft een geciteerde string altijd door als een enkel argument, waarbij puntkomma’s intact worden gelaten:
set(ARGS "EXPR;T;1 + 1")message("${ARGS}") # Prints: EXPR;T;1 + 1
Als meer dan twee argumenten worden doorgegeven aan het set
commando, worden ze samengevoegd door puntkomma’s, en dan toegewezen aan de gespecificeerde variabele. Dit creëert effectief een lijst van de argumenten:
set(MY_LIST These are separate arguments)message("${MY_LIST}") # Prints: These;are;separate;arguments
U kunt dergelijke lijsten manipuleren met het list
commando:
set(MY_LIST These are separate arguments)list(REMOVE_ITEM MY_LIST "separate") # Removes "separate" from the listmessage("${MY_LIST}") # Prints: These;are;arguments
Het foreach
/endforeach
commando accepteert meerdere argumenten. Het loopt door alle argumenten behalve het eerste, en wijst elk toe aan de genoemde variabele:
foreach(ARG These are separate arguments) message("${ARG}") # Prints each word on a separate lineendforeach()
U kunt door een lijst lopen door een niet-gequoteerde verwijzing naar een variabele aan foreach
door te geven. Zoals met elk ander commando, zal CMake de waarde van de variabele splitsen en meerdere argumenten aan het commando doorgeven:
foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments message("${ARG}") # Prints each item on a separate lineendforeach()
Functies draaien in hun eigen bereik; Macro’s niet
In CMake, kunt u een paar function
/endfunction
commando’s gebruiken om een functie te definiëren. Hier is er een die de numerieke waarde van zijn argument verdubbelt, en dan het resultaat afdrukt:
function(doubleIt VALUE) math(EXPR RESULT "${VALUE} * 2") message("${RESULT}")endfunction()doubleIt("4") # Prints: 8
Functies draaien in hun eigen bereik. Geen van de variabelen gedefinieerd in een functie vervuilen het bereik van de aanroeper. Als u een waarde wilt retourneren, kunt u de naam van een variabele aan uw functie doorgeven, en vervolgens het set
commando aanroepen met het speciale argument 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
Op vergelijkbare wijze definieert een paar macro
/endmacro
commando’s een macro. In tegenstelling tot functies, werken macro’s in hetzelfde bereik als hun aanroeper. Daarom worden alle variabelen die in een macro gedefinieerd zijn, ingesteld in het bereik van de aanroeper. We kunnen de vorige functie vervangen door de volgende:
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
Zowel functies als macro’s accepteren een willekeurig aantal argumenten. Onbenoemde argumenten worden aan de functie getoond als een lijst, via een speciale variabele met de naam ARGN
. Hier is een functie die elk ontvangen argument verdubbelt, en elk argument op een aparte regel afdrukt:
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
Inclusief andere scripts
CMake variabelen worden gedefinieerd op bestandsbereik. Het include
commando voert een ander CMake script uit in dezelfde scope als het aanroepende script. Het lijkt veel op de #include
directief in C/C++. Het wordt meestal gebruikt om een gemeenschappelijke set van functies of macro’s in het aanroepende script te definiëren. Het gebruikt de variabele CMAKE_MODULE_PATH
als een zoekpad.
Het find_package
commando zoekt naar scripts van de vorm Find*.cmake
en voert ze ook uit in hetzelfde bereik. Dergelijke scripts worden vaak gebruikt om externe bibliotheken te helpen vinden. Als er bijvoorbeeld een bestand met de naam FindSDL2.cmake
in het zoekpad staat, is find_package(SDL2)
gelijk aan include(FindSDL2.cmake)
. (Merk op dat er verschillende manieren zijn om het find_package
commando te gebruiken – dit is er slechts een van.)
CMake’s add_subdirectory
commando, aan de andere kant, maakt een nieuwe scope, voert dan het script genaamd CMakeLists.txt
uit vanuit de gespecificeerde directory in die nieuwe scope. Je gebruikt het meestal om een ander CMake-gebaseerd subproject, zoals een library of executable, toe te voegen aan het aanroepende project. De targets gedefinieerd door het subproject worden toegevoegd aan de bouwpijplijn, tenzij anders aangegeven. Geen van de variabelen gedefinieerd in het script van het subproject zal de scope van het bovenliggende project vervuilen, tenzij de set
commando’s PARENT_SCOPE
optie wordt gebruikt.
Als voorbeeld zijn hier enkele van de scripts die betrokken zijn wanneer u CMake uitvoert op het Turf project:
Getting and Setting Properties
Een CMake script definieert targets met de add_executable
, add_library
of add_custom_target
commando’s. Zodra een target is gemaakt, heeft het eigenschappen die u kunt manipuleren met de get_property
en set_property
commando’s. In tegenstelling tot variabelen, zijn targets zichtbaar in elk bereik, zelfs als ze zijn gedefinieerd in een subdirectory. Alle doel-eigenschappen zijn strings.
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
Andere doel-eigenschappen zijn LINK_LIBRARIES
, INCLUDE_DIRECTORIES
en COMPILE_DEFINITIONS
. Deze eigenschappen worden, indirect, gewijzigd door de target_link_libraries
, target_include_directories
en target_compile_definitions
commando’s. Aan het eind van het script, gebruikt CMake deze doel-eigenschappen om de bouw pijplijn te genereren.
Er zijn ook eigenschappen voor andere CMake entiteiten. Er is een set van directory eigenschappen voor elk bestandsscope. Er is een set van globale eigenschappen die toegankelijk is vanuit alle scripts. En er is een set van bronbestand eigenschappen voor elk C/C++ bronbestand.