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.