Kuten edellisessä viestissäni selitettiin, jokaisen CMake-pohjaisen projektin on sisällettävä komentosarja nimeltä CMakeLists.txt
. Tämä skripti määrittelee kohteet, mutta se voi tehdä myös monia muita asioita, kuten etsiä kolmannen osapuolen kirjastoja tai luoda C++-otsikkotiedostoja. CMake-skripteissä on paljon joustavuutta.
Joka kerta, kun integroit ulkoisen kirjaston, ja usein, kun lisäät tukea toiselle alustalle, sinun täytyy muokata skriptiä. Vietin pitkän aikaa CMake-skriptien muokkaamisessa ymmärtämättä kieltä kunnolla, sillä dokumentaatio on melko hajanaista, mutta lopulta asiat napsahtivat. Tämän postauksen tavoitteena on saada sinut samaan pisteeseen mahdollisimman nopeasti.
Tämä postaus ei kata kaikkia CMaken sisäänrakennettuja komentoja, sillä niitä on satoja, mutta se on melko kattava opas kielen syntaksista ja ohjelmointimallista.
Hello World
Jos luot tiedoston nimeltä hello.txt
, jonka sisältö on seuraava:
message("Hello world!") # A message to print
…voit ajaa sen komentoriviltä käyttämällä cmake -P hello.txt
. (Vaihtoehto -P
ajaa annetun komentosarjan, mutta ei luo rakennusputkea). Odotetusti se tulostaa ”Hello world!”.
$ cmake -P hello.txtHello world!
All Variables Are Strings
Cmakessa jokainen muuttuja on merkkijono. Voit korvata muuttujan merkkijonokirjaimen sisällä ympäröimällä sen ${}
:llä. Tätä kutsutaan muuttujaviittaukseksi. Muokkaa hello.txt
seuraavasti:
message("Hello ${NAME}!") # Substitute a variable into the message
Nyt jos määrittelemme NAME
komentorivillä cmake
käyttäen -D
-optiota, skripti käyttää sitä:
$ cmake -DNAME=Newman -P hello.txtHello Newman!
Kun muuttuja on määrittelemätön, sen oletusarvona on tyhjä merkkijono:
$ cmake -P hello.txtHello !
Muuttujan määrittelemiseksi skriptin sisälle käytetään set
-komentoa. Ensimmäinen argumentti on määritettävän muuttujan nimi ja toinen argumentti on sen arvo:
set(THING "funk")message("We want the ${THING}!")
Lausekkeet argumenttien ympärillä ovat valinnaisia, kunhan argumentissa ei ole välilyöntejä tai muuttujaviittauksia. Olisin esimerkiksi voinut kirjoittaa yllä olevalle ensimmäiselle riville set("THING" funk)
– se olisi ollut vastaava. Useimmissa CMake-komennoissa (paitsi if
ja while
, jotka kuvataan jäljempänä) valinta siitä, lainataanko tällaiset argumentit, on yksinkertaisesti tyylikysymys. Kun argumentti on muuttujan nimi, minulla on tapana olla käyttämättä lainausmerkkejä.
Voit simuloida tietorakennetta käyttämällä etuliitteitä
CMakessa ei ole luokkia, mutta voit simuloida tietorakennetta määrittelemällä ryhmän muuttujia, joiden nimet alkavat samalla etuliitteellä. Voit sitten etsiä muuttujia tuosta ryhmästä käyttämällä sisäkkäisiä ${}
-muuttujaviittauksia. Esimerkiksi seuraava skripti tulostaa ”John Smith asuu osoitteessa 123 Fake St.”:
set(JOHN_NAME "John Smith")set(JOHN_ADDRESS "123 Fake St")set(PERSON "JOHN")message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")
Voit jopa käyttää muuttujaviittauksia asetettavan muuttujan nimessä. Jos esimerkiksi PERSON
:n arvo on edelleen ”JOHN”, seuraava asettaa muuttujan JOHN_NAME
arvoksi ”John Goodman”:
set(${PERSON}_NAME "John Goodman")
Jokainen lauseke on komento
CMakessa jokainen lauseke on komento, joka ottaa listan merkkijonoargumentteja eikä sillä ole paluuarvoa. Argumentit erotetaan toisistaan (ilman lainausmerkkejä) välilyönneillä. Kuten olemme jo nähneet, komento set
määrittelee muuttujan tiedostoalueelle.
Muuna esimerkkinä CMakessa on komento math
, joka suorittaa aritmeettisia laskutoimituksia. Ensimmäisen argumentin on oltava EXPR
, toinen argumentti on osoitettavan muuttujan nimi ja kolmas argumentti on arvioitava lauseke – kaikki merkkijonoja. Huomaa, että alla olevalla kolmannella rivillä CMake korvaa merkkijonon MY_SUM
arvon ympäröivään argumenttiin ennen argumentin välittämistä math
:lle.
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}.")
Tässä on CMake-komento melkein kaikkeen, mitä sinun tarvitsee tehdä. string
-komennolla voit suorittaa edistynyttä merkkijonojen käsittelyä, mukaan lukien säännöllisten lausekkeiden korvaaminen. Komennolla file
voi lukea tai kirjoittaa tiedostoja tai käsitellä tiedostojärjestelmän polkuja.
Virtauksenohjauskomennot
Jopa virtauksenohjauslausekkeet ovat komentoja. Komennot if
/endif
suorittavat liitteenä olevat komennot ehdollisesti. Välilyönneillä ei ole väliä, mutta on tavallista, että suljetut komennot sisennetään luettavuuden vuoksi. Seuraavassa tarkistetaan, onko CMaken sisäänrakennettu muuttuja WIN32
asetettu:
if(WIN32) message("You're running CMake on Windows.")endif()
CMakella on myös while
/endwhile
-komennot, jotka, kuten arvata saattaa, toistavat suljettuja komentoja niin kauan kuin ehto on tosi. Tässä on silmukka, joka tulostaa kaikki Fibonaccin luvut miljoonaan asti:
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()
CMaken if
– ja while
-ehtoja ei kirjoiteta samalla tavalla kuin muissa kielissä. Jos haluat esimerkiksi suorittaa numeerisen vertailun, sinun on määritettävä LESS
merkkijonoargumenttina, kuten yllä on esitetty. Dokumentaatiossa kerrotaan, miten kelvollinen ehto kirjoitetaan.
if
ja while
eroavat muista CMake-komennoista siinä, että jos muuttujan nimi annetaan ilman lainausmerkkejä, komento käyttää muuttujan arvoa. Yllä olevassa koodissa hyödynsin tätä käyttäytymistä kirjoittamalla while(A LESS "1000000")
while("${A}" LESS "1000000")
:n sijasta – molemmat muodot vastaavat toisiaan. Muut CMake-komennot eivät tee näin.
Luettelot ovat vain puolipisteellä rajattuja merkkijonoja
CMakella on erityinen korvaussääntö lainausmerkeillä rajoittamattomille argumenteille. Jos koko argumentti on muuttujaviittaus ilman lainausmerkkejä ja muuttujan arvo sisältää puolipisteitä, CMake jakaa arvon puolipisteiden kohdalla ja välittää useita argumentteja ympäröivälle komennolle. Esimerkiksi seuraavassa välitetään kolme argumenttia komennolle math
:
set(ARGS "EXPR;T;1 + 1")math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")
Toisaalta lainausmerkeillä varustettuja argumentteja ei koskaan jaeta useammaksi argumentiksi, ei edes korvaamisen jälkeen. CMake välittää lainausmerkkisen merkkijonon aina yhtenä argumenttina jättäen puolipisteet koskemattomiksi:
set(ARGS "EXPR;T;1 + 1")message("${ARGS}") # Prints: EXPR;T;1 + 1
Jos komennolle set
välitetään enemmän kuin kaksi argumenttia, ne yhdistetään puolipisteillä ja osoitetaan sitten määritettyyn muuttujaan. Näin argumenteista luodaan käytännössä lista:
set(MY_LIST These are separate arguments)message("${MY_LIST}") # Prints: These;are;separate;arguments
Voit käsitellä tällaisia listoja komennolla 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
Komento foreach
/endforeach
hyväksyy useita argumentteja. Se iteroi kaikki argumentit ensimmäistä lukuun ottamatta ja osoittaa jokaisen nimettyyn muuttujaan:
foreach(ARG These are separate arguments) message("${ARG}") # Prints each word on a separate lineendforeach()
Voit iteroida listaa välittämällä foreach
:lle noteeraamattoman muuttujan viittauksen. Kuten minkä tahansa muun komennon kohdalla, CMake jakaa muuttujan arvon ja välittää komennolle useita argumentteja:
foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments message("${ARG}") # Prints each item on a separate lineendforeach()
Functions Run In Their Own Scope; Macros Don’t
Cmake:ssa voit käyttää komentoparia function
/endfunction
funktion määrittelyyn. Tässä on yksi, joka kaksinkertaistaa argumenttinsa numeerisen arvon ja tulostaa tuloksen:
function(doubleIt VALUE) math(EXPR RESULT "${VALUE} * 2") message("${RESULT}")endfunction()doubleIt("4") # Prints: 8
Funktiot toimivat omassa laajuudessaan. Mikään funktiossa määritellyistä muuttujista ei saastuta kutsujan vaikutusaluetta. Jos haluat palauttaa arvon, voit antaa funktiolle muuttujan nimen ja kutsua sitten komentoa set
erikoisargumentilla 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
Vastaavasti komentopari macro
/endmacro
määrittelee makron. Toisin kuin funktiot, makrot toimivat samassa laajuudessa kuin niiden kutsuja. Siksi kaikki makron sisällä määritellyt muuttujat asetetaan kutsujan vaikutusalueelle. Voimme korvata edellisen funktion seuraavalla:
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
Kumpikin funktio ja makro hyväksyvät mielivaltaisen määrän argumentteja. Nimettömät argumentit näytetään funktiolle listana erityisen muuttujan ARGN
kautta. Tässä on funktio, joka tuplaa jokaisen saamansa argumentin ja tulostaa jokaisen argumentin erilliselle riville:
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
Sisällyttää muita skriptejä
CMake-muuttujat määritellään tiedoston laajuudessa. Komento include
suorittaa toisen CMake-skriptin samassa laajuudessa kuin kutsuva skripti. Se muistuttaa paljon #include
-direktiiviä C/C++:ssa. Sitä käytetään yleensä määrittelemään yhteinen joukko funktioita tai makroja kutsuvassa komentosarjassa. Se käyttää muuttujaa CMAKE_MODULE_PATH
hakupolkuna.
find_package
Komento find_package
etsii skriptejä, jotka ovat muotoa Find*.cmake
ja ajaa myös ne samassa laajuudessa. Tällaisia skriptejä käytetään usein apuna ulkoisten kirjastojen etsimisessä. Jos hakupolussa on esimerkiksi tiedosto nimeltä FindSDL2.cmake
, find_package(SDL2)
vastaa include(FindSDL2.cmake)
. (Huomaa, että komennon find_package
käyttämiseen on useita tapoja – tämä on vain yksi niistä.)
CMaken komento add_subdirectory
puolestaan luo uuden scope-alueen ja suorittaa sitten skriptin nimeltä CMakeLists.txt
määritellystä hakemistosta tässä uudessa scope-alueessa. Sitä käytetään yleensä lisäämään toinen CMake-pohjainen aliprojekti, kuten kirjasto tai suoritettava tiedosto, kutsuvaan projektiin. Aliprojektin määrittelemät kohteet lisätään rakennusputkeen, ellei toisin määritetä. Mikään aliprojektin komentosarjassa määritetyistä muuttujista ei saastuta emoprojektin scope-aluetta, ellei komennon set
PARENT_SCOPE
-optiota käytetä.
Esimerkiksi seuraavassa on joitakin komentosarjoja, jotka ovat mukana, kun ajetaan CMakea Turf-projektissa:
Ominaisuuksien saaminen ja asettaminen
CMake-skripti määrittelee kohteet komennoilla add_executable
, add_library
tai add_custom_target
. Kun kohde on luotu, sillä on ominaisuuksia, joita voit käsitellä get_property
– ja set_property
-komennoilla. Toisin kuin muuttujat, kohteet näkyvät jokaisessa laajuudessa, vaikka ne olisi määritelty alihakemistossa. Kaikki kohdeominaisuudet ovat merkkijonoja.
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
Muut kohdeominaisuudet ovat LINK_LIBRARIES
, INCLUDE_DIRECTORIES
ja COMPILE_DEFINITIONS
. Näitä ominaisuuksia muutetaan epäsuorasti komennoilla target_link_libraries
, target_include_directories
ja target_compile_definitions
. Skriptin lopussa CMake käyttää näitä kohdeominaisuuksia rakentamisputken luomiseen.
Ominaisuuksia on myös muille CMake-olioille. Jokaisella tiedostoalueella on joukko hakemisto-ominaisuuksia. On joukko globaaleja ominaisuuksia, joihin pääsee käsiksi kaikista skripteistä. Ja jokaiselle C/C++-lähdetiedostolle on joukko lähdetiedoston ominaisuuksia.