Jak jsem vysvětlil v předchozím příspěvku, každý projekt založený na CMake musí obsahovat skript s názvem CMakeLists.txt. Tento skript definuje cíle, ale může také dělat spoustu dalších věcí, například vyhledávat knihovny třetích stran nebo generovat hlavičkové soubory C++. Skripty CMake mají velkou flexibilitu.

Při každé integraci externí knihovny a často i při přidávání podpory pro jinou platformu budete muset skript upravit. Strávil jsem dlouhou dobu úpravami skriptů CMake, aniž bych jazyku pořádně rozuměl, protože dokumentace je dost roztříštěná, ale nakonec mi to docvaklo. Cílem tohoto příspěvku je dostat vás co nejrychleji do stejného bodu.

Tento příspěvek se nebude zabývat všemi vestavěnými příkazy jazyka CMake, protože jich jsou stovky, ale je to poměrně kompletní průvodce syntaxí a programovacím modelem jazyka.

Hello World

Pokud vytvoříte soubor s názvem hello.txt s následujícím obsahem:

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

…můžete jej spustit z příkazového řádku pomocí cmake -P hello.txt. (Volba -P spustí daný skript, ale nevytvoří sestavovací sestavení). Podle očekávání vypíše „Hello world!“.

$ cmake -P hello.txtHello world!

Všechny proměnné jsou řetězce

V CMake je každá proměnná řetězec. Proměnnou můžete nahradit uvnitř řetězcového literálu tak, že ji obklopíte ${}. Tomu se říká odkaz na proměnnou. Upravte hello.txt takto:

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

Pokud nyní definujeme NAME na příkazovém řádku cmake pomocí volby -D, skript ji použije:

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

Když je proměnná nedefinovaná, je výchozí hodnotou prázdný řetězec:

$ cmake -P hello.txtHello !

Pro definování proměnné uvnitř skriptu použijte příkaz set. Prvním argumentem je název proměnné, kterou chcete přiřadit, a druhým argumentem je její hodnota:

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

Uvozovky kolem argumentů jsou nepovinné, pokud v argumentu nejsou mezery nebo odkazy na proměnné. Například v prvním řádku výše jsem mohl napsat set("THING" funk) – bylo by to ekvivalentní. U většiny příkazů CMake (kromě if a while, které jsou popsány níže) je volba, zda takové argumenty uvádět v uvozovkách, jednoduše otázkou stylu. Pokud je argumentem jméno proměnné, mám tendenci uvozovky nepoužívat.

Můžete simulovat datovou strukturu pomocí prefixů

CMake nemá třídy, ale můžete simulovat datovou strukturu definováním skupiny proměnných se jmény začínajícími stejným prefixem. Proměnné v této skupině pak můžete vyhledávat pomocí vnořených odkazů na proměnné ${}. Například následující skript vypíše „John Smith bydlí na adrese 123 Fake St.“:

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

Můžete dokonce použít odkazy na proměnné v názvu proměnné, kterou chcete nastavit. Pokud je například hodnota PERSON stále „JOHN“, následující příkaz nastaví proměnnou JOHN_NAME na „John Goodman“:

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

Každý příkaz je příkaz

V systému CMake je každý příkaz příkazem, který přijímá seznam řetězcových argumentů a nemá žádnou návratovou hodnotu. Argumenty jsou odděleny (necitovanými) mezerami. Jak jsme již viděli, příkaz set definuje proměnnou v rozsahu souboru.

Jako další příklad má CMake příkaz math, který provádí aritmetiku. Prvním argumentem musí být EXPR, druhým argumentem je jméno proměnné, která se má přiřadit, a třetím argumentem je výraz, který se má vyhodnotit – vše řetězce. Všimněte si, že na třetím řádku níže nahradí CMake řetězcovou hodnotu MY_SUM do uzavírajícího argumentu před předáním argumentu 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}.")

Příkaz CMake existuje téměř pro všechno, co budete potřebovat udělat. Příkaz string umožňuje provádět pokročilou manipulaci s řetězci, včetně nahrazování regulárních výrazů. Příkaz file umí číst a zapisovat soubory nebo manipulovat s cestami k souborovému systému.

Příkazy řízení toku

I příkazy řízení toku jsou příkazy. Příkazy if/endif provádějí přiložené příkazy podmíněně. Na bílých místech nezáleží, ale je běžné, že se přiložené příkazy kvůli čitelnosti odsazují. Následující příkaz kontroluje, zda je nastavena vestavěná proměnná CMake WIN32:

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

CMake má také příkazy while/endwhile, které, jak lze očekávat, opakují uzavřené příkazy, pokud je podmínka pravdivá. Zde je smyčka, která vypíše všechna Fibonacciho čísla až do jednoho milionu:

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()

Podmínky if a while v jazyce CMake nejsou zapsány stejným způsobem jako v jiných jazycích. Chcete-li například provést číselné porovnání, musíte zadat LESS jako řetězcový argument, jak je uvedeno výše. V dokumentaci je vysvětleno, jak zapsat správnou podmínku.

if a while se od ostatních příkazů CMake liší tím, že pokud je jméno proměnné uvedeno bez uvozovek, příkaz použije hodnotu proměnné. Ve výše uvedeném kódu jsem tohoto chování využil tím, že jsem místo while("${A}" LESS "1000000") napsal while(A LESS "1000000") – oba tvary jsou ekvivalentní. Jiné příkazy CMake to nedělají.

Seznamy jsou jen řetězce ohraničené středníkem

CMake má pro argumenty bez uvozovek zvláštní pravidlo nahrazování. Pokud je celý argument odkazem na proměnnou bez uvozovek a hodnota proměnné obsahuje středníky, CMake rozdělí hodnotu na středníky a předá obklopujícímu příkazu více argumentů. Například následující příkaz předá tři argumenty příkazu math:

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

Naproti tomu argumenty s uvozovkami se nikdy nerozdělí na více argumentů, a to ani po záměně. CMake vždy předává uvozený řetězec jako jediný argument, přičemž středníky ponechává nedotčené:

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

Pokud jsou příkazu set předány více než dva argumenty, jsou spojeny středníky a poté přiřazeny zadané proměnné. Tím se z argumentů vlastně vytvoří seznam:

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

S takovými seznamy lze manipulovat pomocí příkazu 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

Příkaz foreach/endforeach přijímá více argumentů. Prochází všechny argumenty kromě prvního a každý z nich přiřadí pojmenované proměnné:

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

Seznam můžete procházet předáním necitovaného odkazu na proměnnou příkazu foreach. Stejně jako u jiných příkazů CMake rozdělí hodnotu proměnné a předá příkazu více argumentů:

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

Funkce běží ve vlastním oboru; makra ne

V CMake můžete k definici funkce použít dvojici příkazů function/endfunction. Zde je jeden, který zdvojnásobí číselnou hodnotu svého argumentu a poté vypíše výsledek:

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

Funkce běží ve vlastním oboru. Žádná z proměnných definovaných ve funkci neznečišťuje obor volajícího. Pokud chcete vrátit hodnotu, můžete funkci předat jméno proměnné a pak zavolat příkaz set se speciálním argumentem 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

Podobně dvojice příkazů macro/endmacro definuje makro. Na rozdíl od funkcí se makra spouštějí ve stejném oboru jako jejich volající. Proto jsou všechny proměnné definované uvnitř makra nastaveny v oboru volajícího. Předchozí funkci můžeme nahradit následujícím:

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

Jak funkce, tak makra přijímají libovolný počet argumentů. Nepojmenované argumenty jsou funkci vystaveny jako seznam, a to prostřednictvím speciální proměnné s názvem ARGN. Zde je funkce, která zdvojnásobí každý přijatý argument a každý z nich vypíše na samostatný řádek:

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

Včetně dalších skriptů

Proměnné programu Make jsou definovány v rozsahu souboru. Příkaz include spustí jiný skript CMake ve stejném oboru jako volající skript. Je to něco podobného jako direktiva #include v jazyce C/C++. Obvykle se používá k definování společné sady funkcí nebo maker ve volajícím skriptu. Jako vyhledávací cestu používá proměnnou CMAKE_MODULE_PATH.

Příkaz find_package vyhledává skripty ve tvaru Find*.cmake a také je spouští ve stejném oboru. Takové skripty se často používají jako pomoc při vyhledávání externích knihoven. Pokud se například ve vyhledávací cestě nachází soubor s názvem FindSDL2.cmake, find_package(SDL2) je ekvivalentní include(FindSDL2.cmake). (Všimněte si, že existuje několik způsobů použití příkazu find_package – toto je jen jeden z nich.)

Příkaz add_subdirectoryCMake naopak vytvoří nový obor a v tomto novém oboru spustí skript s názvem CMakeLists.txt ze zadaného adresáře. Obvykle jej používáte k přidání dalšího podprojektu založeného na CMake, například knihovny nebo spustitelného souboru, do volajícího projektu. Cíle definované podprojektem se přidají do sestavovacího potrubí, pokud není uvedeno jinak. Žádná z proměnných definovaných ve skriptu podprojektu neznečistí obor nadřazeného projektu, pokud není použita volba PARENT_SCOPE příkazu set.

Na příklad uvádíme některé skripty zapojené při spuštění CMake na projektu Turf:

Získání a nastavení vlastností

Skript CMake definuje cíle pomocí příkazů add_executable, add_library nebo add_custom_target. Jakmile je cíl vytvořen, má vlastnosti, se kterými můžete manipulovat pomocí příkazů get_property a set_property. Na rozdíl od proměnných jsou cíle viditelné v každém oboru, i když byly definovány v podadresáři. Všechny vlastnosti cíle jsou řetězce.

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

Mezi další vlastnosti cíle patří LINK_LIBRARIES, INCLUDE_DIRECTORIES a COMPILE_DEFINITIONS. Tyto vlastnosti jsou nepřímo modifikovány příkazy target_link_libraries, target_include_directories a target_compile_definitions. Na konci skriptu použije CMake tyto cílové vlastnosti k vytvoření sestavovacího potrubí.

Existují také vlastnosti pro další entity CMake. U každého oboru souboru je k dispozici sada vlastností adresáře. Existuje sada globálních vlastností, která je přístupná ze všech skriptů. A existuje sada vlastností zdrojových souborů pro každý zdrojový soubor jazyka C/C++.

Articles

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.