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.

Articles

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.