Amint azt az előző bejegyzésemben kifejtettem, minden CMake-alapú projektnek tartalmaznia kell egy CMakeLists.txt nevű szkriptet. Ez a szkript definiálja a célokat, de sok más dolgot is elvégezhet, például harmadik féltől származó könyvtárak keresését vagy C++ fejlécfájlok generálását. A CMake szkriptek nagy rugalmassággal rendelkeznek.

Minden alkalommal, amikor külső könyvtárat integrálsz, és gyakran akkor is, amikor egy másik platform támogatását adod hozzá, szerkesztened kell a szkriptet. Hosszú időt töltöttem a CMake szkriptek szerkesztésével anélkül, hogy igazán értettem volna a nyelvet, mivel a dokumentáció eléggé szétszórt, de végül kattantak a dolgok. Ennek a bejegyzésnek az a célja, hogy a lehető leggyorsabban eljusson ugyanoda.

Ez a bejegyzés nem fogja lefedni a CMake összes beépített parancsát, mivel több száz van belőlük, de egy meglehetősen teljes útmutató a nyelv szintaxisához és programozási modelljéhez.

Hello World

Ha létrehozunk egy hello.txt nevű fájlt a következő tartalommal:

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

…akkor a parancssorból a cmake -P hello.txt segítségével futtathatjuk. (A -P opció lefuttatja az adott szkriptet, de nem hoz létre build pipeline-t.) A várakozásoknak megfelelően kiírja, hogy “Hello world!”.

$ cmake -P hello.txtHello world!

All Variables Are Strings

A CMake-ben minden változó egy string. Egy változót egy string literálon belül úgy helyettesíthetünk, hogy ${}-tel körülvesszük. Ezt nevezzük változóhivatkozásnak. Módosítsuk a hello.txt-t a következőképpen:

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

Ha most a cmake parancssoron a -D opcióval definiáljuk a NAME-t, a szkript használni fogja:

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

Ha egy változó nincs definiálva, akkor alapértelmezés szerint üres karakterláncot kap:

$ cmake -P hello.txtHello !

A szkriptben lévő változó definiálásához használjuk a set parancsot. Az első argumentum a hozzárendelni kívánt változó neve, a második pedig annak értéke:

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

Az argumentumok körüli idézőjelek opcionálisak, amennyiben nincsenek szóközök vagy változóhivatkozások az argumentumban. Például a fenti első sorban írhattam volna set("THING" funk) – ez egyenértékű lett volna. A legtöbb CMake-parancs esetében (kivéve a if és while parancsokat, amelyeket alább ismertetünk), az ilyen argumentumok idézőjelbe helyezése egyszerűen stílus kérdése. Amikor az argumentum egy változó neve, én nem szoktam idézőjeleket használni.

Adatstruktúrát szimulálhatsz prefixekkel

A CMake nem rendelkezik osztályokkal, de szimulálhatsz adatstruktúrát, ha olyan változók csoportját definiálod, amelyek neve azonos prefixszel kezdődik. Ezután a csoportban lévő változókat egymásba ágyazott ${} változóhivatkozásokkal kereshetjük meg. Például a következő szkript kiírja, hogy “John Smith a 123 Fake St. címen lakik”:

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

A beállítandó változó nevében is használhatunk változóhivatkozásokat. Például, ha a PERSON értéke még mindig “JOHN”, a következő a JOHN_NAME változót “John Goodman”-re állítja:

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

Minden utasítás egy parancs

A CMake-ben minden utasítás egy parancs, amely egy listát fogad el a string argumentumokból, és nincs visszatérési értéke. Az argumentumok (idézőjel nélküli) szóközökkel vannak elválasztva. Mint már láttuk, a set parancs egy változót definiál a fájl hatókörében.

A CMake-nek van egy másik példája, a math parancs, amely aritmetikát végez. Az első argumentumnak EXPR kell lennie, a második argumentum a hozzárendelni kívánt változó neve, a harmadik argumentum pedig az értékelendő kifejezés – mind karakterlánc. Figyeljük meg, hogy az alábbi harmadik sorban a CMake a MY_SUM string értékét behelyettesíti a körülvevő argumentumba, mielőtt az argumentumot átadja a math-nak.

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}.")

Majdnem mindenre van CMake parancs, amire szükségünk lehet. A string paranccsal fejlett karakterlánc-manipulációt végezhetünk, beleértve a reguláris kifejezések cseréjét is. A file paranccsal fájlokat olvashatunk vagy írhatunk, illetve fájlrendszeri elérési utakat manipulálhatunk.

Folyamszabályozási parancsok

Még a folyamszabályozási utasítások is parancsok. A if/endif parancsok a mellékelt parancsokat feltételesen hajtják végre. A szóköz nem számít, de a mellékelt parancsokat az olvashatóság érdekében szokás behúzni. A következő ellenőrzi, hogy a CMake beépített WIN32 változója be van-e állítva:

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

A CMake-nek vannak while/endwhile parancsai is, amelyek, ahogyan az várható volt, megismétlik a mellékelt parancsokat, amíg a feltétel igaz. Íme egy ciklus, amely kiírja az összes Fibonacci-számot egymillióig:

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 if és while feltételei nem ugyanúgy vannak leírva, mint más nyelvekben. Például egy numerikus összehasonlítás elvégzéséhez a fenti módon a LESS-et kell megadni string argumentumként. A dokumentáció elmagyarázza, hogyan kell érvényes feltételt írni.

if és while abban különbözik más CMake-parancsoktól, hogy ha egy változó nevét idézőjelek nélkül adjuk meg, a parancs a változó értékét fogja használni. A fenti kódban ezt a viselkedést úgy használtam ki, hogy while("${A}" LESS "1000000") helyett while(A LESS "1000000")-t írtam – mindkét forma egyenértékű. Más CMake-parancsok ezt nem teszik meg.

A listák csak pontosvesszővel elválasztott karakterláncok

A CMake-nek van egy speciális helyettesítési szabálya az idézőjel nélküli argumentumokra. Ha a teljes argumentum egy idézőjelek nélküli változóhivatkozás, és a változó értéke pontosvesszőket tartalmaz, a CMake az értéket a pontosvesszőknél szétválasztja, és több argumentumot ad át a körülvevő parancsnak. Például a következő három argumentumot ad át a math parancsnak:

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

Az idézőjeles argumentumokat viszont soha nem osztja fel több argumentumra, még a helyettesítés után sem. A CMake az idézett karakterláncot mindig egyetlen argumentumként adja át, a pontosvesszőket érintetlenül hagyva:

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

Ha kettőnél több argumentumot adunk át a set parancsnak, azokat pontosvesszőkkel egyesíti, majd hozzárendeli a megadott változóhoz. Ez gyakorlatilag egy listát hoz létre az argumentumokból:

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

Az ilyen listákat a list paranccsal manipulálhatjuk:

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

A foreach/endforeach parancs több argumentumot is elfogad. Az első kivételével minden argumentumon végigmegy, és mindegyiket hozzárendeli a megnevezett változóhoz:

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

Listákon iterálhatsz, ha egy idézőjel nélküli változóhivatkozást adsz át a foreach parancsnak. Mint bármely más parancs esetében, a CMake felosztja a változó értékét, és több argumentumot ad át a parancsnak:

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

A függvények saját hatókörükben futnak; a makrók nem

A CMake-ben a function/endfunction parancspárt használhatjuk egy függvény definiálására. Íme egy olyan, amely megduplázza az argumentum numerikus értékét, majd kiírja az eredményt:

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

A függvények a saját hatókörükben futnak. A függvényben definiált változók egyike sem szennyezi a hívó hatókörét. Ha értéket akarunk visszaadni, akkor átadhatjuk egy változó nevét a függvényünknek, majd a set parancsot a PARENT_SCOPE speciális argumentummal hívjuk meg:

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

Hasonlóképpen, egy macro/endmacro parancspár makrót definiál. A függvényektől eltérően a makrók a hívójukkal azonos hatókörben futnak. Ezért a makrón belül definiált változók a hívó hatókörében kerülnek beállításra. Az előző függvényt a következőkkel helyettesíthetjük:

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

Mind a függvények, mind a makrók tetszőleges számú argumentumot fogadnak el. A meg nem nevezett argumentumok a függvény számára listaként jelennek meg, egy speciális, ARGN nevű változón keresztül. Íme egy függvény, amely minden kapott argumentumot megdupláz, és mindegyiket külön sorba írja ki:

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

Más szkriptek bevonása

CMake változókat a fájl hatókörében definiáljuk. A include parancs egy másik CMake szkriptet hajt végre a hívó szkript hatókörében. Nagyon hasonlít a #include utasításhoz a C/C++-ban. Általában a hívó szkriptben lévő függvények vagy makrók közös készletének definiálására használják. A CMAKE_MODULE_PATH változót használja keresési útvonalként.

A find_package parancs a Find*.cmake formájú szkripteket keresi, és azokat is ugyanabban a hatókörben futtatja. Az ilyen szkripteket gyakran használják külső könyvtárak keresésének segítésére. Ha például a keresési útvonalban van egy FindSDL2.cmake nevű fájl, akkor a find_package(SDL2) egyenértékű a include(FindSDL2.cmake)-val. (Megjegyzendő, hogy a find_package parancs használatának több módja is van – ez csak egy ezek közül.)

A add_subdirectory parancs a CMake-nél viszont létrehoz egy új hatókör (scope), majd ebben az új hatókörben végrehajtja a CMakeLists.txt nevű szkriptet a megadott könyvtárból. Általában arra használjuk, hogy a hívó projekthez egy másik CMake-alapú alprojektet, például egy könyvtárat vagy végrehajtható fájlt adjunk hozzá. Az alprojekt által definiált célok hozzáadásra kerülnek a build pipeline-hoz, hacsak nincs másképp megadva. Az alprojekt szkriptjében definiált változók egyike sem szennyezi a szülői hatókörét, kivéve, ha a set parancs PARENT_SCOPE opcióját használjuk.

Példaként íme néhány szkript, amely a CMake futtatásakor a Turf projektre vonatkozik:

A tulajdonságok beállítása és beállítása

A CMake szkript a add_executable, add_library vagy add_custom_target parancsok segítségével definiál célokat. Miután létrehoztunk egy céltárgyat, az rendelkezik tulajdonságokkal, amelyeket a get_property és set_property parancsok segítségével manipulálhatunk. A változókkal ellentétben a célok minden hatókörben láthatóak, még akkor is, ha egy alkönyvtárban lettek definiálva. Minden céltulajdonság karakterlánc.

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

A céltulajdonságok közé tartozik még a LINK_LIBRARIES, INCLUDE_DIRECTORIES és COMPILE_DEFINITIONS. Ezeket a tulajdonságokat közvetve a target_link_libraries, target_include_directories és target_compile_definitions parancsok módosítják. A szkript végén a CMake ezeket a céltulajdonságokat használja a build pipeline létrehozásához.

Más CMake entitásoknak is vannak tulajdonságai. Minden fájl hatókörénél van egy könyvtár-tulajdonságkészlet. Van egy globális tulajdonságkészlet, amely minden szkriptből elérhető. És van egy forrásfájl-tulajdonságkészlet minden C/C++ forrásfájlhoz.

Articles

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.