Wie in meinem vorherigen Beitrag erklärt, muss jedes CMake-basierte Projekt ein Skript namens CMakeLists.txt
enthalten. Dieses Skript definiert Targets, aber es kann auch viele andere Dinge tun, wie z.B. Bibliotheken von Drittanbietern finden oder C++-Header-Dateien erzeugen. CMake-Skripte sind sehr flexibel.
Jedes Mal, wenn Sie eine externe Bibliothek integrieren, und oft, wenn Sie Unterstützung für eine andere Plattform hinzufügen, müssen Sie das Skript bearbeiten. Ich habe lange Zeit damit verbracht, CMake-Skripte zu bearbeiten, ohne die Sprache wirklich zu verstehen, da die Dokumentation ziemlich verstreut ist, aber schließlich hat es Klick gemacht. Das Ziel dieses Beitrags ist es, Sie so schnell wie möglich an denselben Punkt zu bringen.
Dieser Beitrag wird nicht alle eingebauten CMake-Befehle abdecken, da es Hunderte gibt, aber er ist eine ziemlich vollständige Anleitung zur Syntax und zum Programmiermodell der Sprache.
Hallo Welt
Wenn Sie eine Datei mit dem Namen hello.txt
mit folgendem Inhalt erstellen:
message("Hello world!") # A message to print
…können Sie sie von der Kommandozeile aus mit cmake -P hello.txt
ausführen. (Die Option -P
führt das angegebene Skript aus, erzeugt aber keine Build-Pipeline). Wie erwartet druckt es „Hello world!“.
$ cmake -P hello.txtHello world!
All Variables Are Strings
In CMake ist jede Variable ein String. Sie können eine Variable innerhalb eines String-Literales ersetzen, indem Sie sie mit ${}
umgeben. Dies wird als Variablenreferenz bezeichnet. Ändern Sie hello.txt
wie folgt:
message("Hello ${NAME}!") # Substitute a variable into the message
Wenn wir nun NAME
in der cmake
-Befehlszeile mit der Option -D
definieren, wird das Skript es verwenden:
$ cmake -DNAME=Newman -P hello.txtHello Newman!
Wenn eine Variable nicht definiert ist, wird sie standardmäßig als leere Zeichenkette verwendet:
$ cmake -P hello.txtHello !
Um eine Variable innerhalb eines Skripts zu definieren, verwenden Sie den Befehl set
. Das erste Argument ist der Name der zuzuweisenden Variable und das zweite Argument ist ihr Wert:
set(THING "funk")message("We want the ${THING}!")
Anführungszeichen um die Argumente sind optional, solange das Argument keine Leerzeichen oder Variablenreferenzen enthält. Ich hätte zum Beispiel set("THING" funk)
in die erste Zeile oben schreiben können – es wäre gleichwertig gewesen. Bei den meisten CMake-Befehlen (außer if
und while
, die weiter unten beschrieben werden) ist die Entscheidung, ob solche Argumente in Anführungszeichen gesetzt werden sollen, einfach eine Frage des Stils. Wenn das Argument der Name einer Variablen ist, tendiere ich dazu, keine Anführungszeichen zu verwenden.
Sie können eine Datenstruktur mit Hilfe von Präfixen simulieren
CMake hat keine Klassen, aber Sie können eine Datenstruktur simulieren, indem Sie eine Gruppe von Variablen definieren, deren Namen mit demselben Präfix beginnen. Sie können dann Variablen in dieser Gruppe mit verschachtelten ${}
-Variablenreferenzen nachschlagen. Das folgende Skript gibt beispielsweise „John Smith wohnt in 123 Fake St.“ aus:
set(JOHN_NAME "John Smith")set(JOHN_ADDRESS "123 Fake St")set(PERSON "JOHN")message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")
Sie können sogar Variablenreferenzen im Namen der zu setzenden Variablen verwenden. Zum Beispiel, wenn der Wert von PERSON
immer noch „JOHN“ ist, wird das Folgende die Variable JOHN_NAME
auf „John Goodman“ setzen:
set(${PERSON}_NAME "John Goodman")
Jede Anweisung ist ein Befehl
In CMake ist jede Anweisung ein Befehl, der eine Liste von String-Argumenten annimmt und keinen Rückgabewert hat. Die Argumente werden durch (unquotierte) Leerzeichen getrennt. Wie wir bereits gesehen haben, definiert der set
-Befehl eine Variable auf Dateiebene.
Als weiteres Beispiel hat CMake einen math
-Befehl, der eine Arithmetik durchführt. Das erste Argument muss EXPR
sein, das zweite Argument ist der Name der zuzuweisenden Variablen und das dritte Argument ist der auszuwertende Ausdruck – alles Zeichenketten. Beachten Sie, dass CMake in der dritten Zeile unten den String-Wert von MY_SUM
in das einschließende Argument einfügt, bevor es das Argument an math
weitergibt.
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}.")
Es gibt einen CMake-Befehl für so ziemlich alles, was Sie tun müssen. Mit dem Befehl string
können Sie erweiterte Stringmanipulationen durchführen, einschließlich der Ersetzung regulärer Ausdrücke. Der Befehl file
kann Dateien lesen oder schreiben oder Dateisystempfade manipulieren.
Flusskontrollbefehle
Auch Flusskontrollanweisungen sind Befehle. Die if
/endif
Befehle führen die eingeschlossenen Befehle bedingt aus. Leerzeichen spielen keine Rolle, aber es ist üblich, die eingeschlossenen Befehle einzurücken, um sie besser lesen zu können. Der folgende Befehl prüft, ob die in CMake eingebaute Variable WIN32
gesetzt ist:
if(WIN32) message("You're running CMake on Windows.")endif()
CMake hat auch while
/endwhile
Befehle, die, wie man erwarten könnte, die eingeschlossenen Befehle wiederholen, solange die Bedingung erfüllt ist. Hier ist eine Schleife, die alle Fibonacci-Zahlen bis zu einer Million ausgibt:
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
und while
Bedingungen sind nicht auf dieselbe Weise geschrieben wie in anderen Sprachen. Um zum Beispiel einen numerischen Vergleich durchzuführen, müssen Sie LESS
als String-Argument angeben, wie oben gezeigt. Die Dokumentation erklärt, wie man eine gültige Bedingung schreibt.
if
und while
unterscheiden sich von anderen CMake-Befehlen insofern, als dass der Befehl den Wert der Variablen verwendet, wenn der Name der Variablen ohne Anführungszeichen angegeben wird. Im obigen Code habe ich dieses Verhalten ausgenutzt, indem ich while(A LESS "1000000")
anstelle von while("${A}" LESS "1000000")
geschrieben habe – beide Formen sind gleichwertig. Andere CMake-Befehle machen das nicht.
Listen sind nur durch Semikolon getrennte Zeichenketten
CMake hat eine spezielle Ersetzungsregel für nicht in Anführungszeichen gesetzte Argumente. Wenn das gesamte Argument ein Variablenverweis ohne Anführungszeichen ist und der Wert der Variable Semikolons enthält, teilt CMake den Wert an den Semikolons auf und übergibt mehrere Argumente an den umschließenden Befehl. Das folgende Beispiel übergibt drei Argumente an math
:
set(ARGS "EXPR;T;1 + 1")math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")
Andererseits werden Argumente in Anführungszeichen niemals in mehrere Argumente aufgeteilt, auch nicht nach der Ersetzung. CMake übergibt eine Zeichenkette in Anführungszeichen immer als ein einziges Argument, wobei Semikolons intakt bleiben:
set(ARGS "EXPR;T;1 + 1")message("${ARGS}") # Prints: EXPR;T;1 + 1
Wenn mehr als zwei Argumente an den Befehl set
übergeben werden, werden sie durch Semikolons verbunden und dann der angegebenen Variablen zugewiesen. Dadurch wird effektiv eine Liste aus den Argumenten erstellt:
set(MY_LIST These are separate arguments)message("${MY_LIST}") # Prints: These;are;separate;arguments
Sie können solche Listen mit dem Befehl list
bearbeiten:
set(MY_LIST These are separate arguments)list(REMOVE_ITEM MY_LIST "separate") # Removes "separate" from the listmessage("${MY_LIST}") # Prints: These;are;arguments
Der Befehl foreach
/endforeach
akzeptiert mehrere Argumente. Er durchläuft alle Argumente außer dem ersten und weist jedes der benannten Variablen zu:
foreach(ARG These are separate arguments) message("${ARG}") # Prints each word on a separate lineendforeach()
Sie können eine Liste durchlaufen, indem Sie foreach
eine nicht in Anführungszeichen stehende Variablenreferenz übergeben. Wie bei jedem anderen Befehl teilt CMake den Wert der Variablen auf und übergibt mehrere Argumente an den Befehl:
foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments message("${ARG}") # Prints each item on a separate lineendforeach()
Funktionen laufen in ihrem eigenen Bereich; Makros nicht
In CMake können Sie ein Paar von function
/endfunction
Befehlen verwenden, um eine Funktion zu definieren. Hier ist eine, die den numerischen Wert ihres Arguments verdoppelt und dann das Ergebnis ausgibt:
function(doubleIt VALUE) math(EXPR RESULT "${VALUE} * 2") message("${RESULT}")endfunction()doubleIt("4") # Prints: 8
Funktionen laufen in ihrem eigenen Bereich. Keine der in einer Funktion definierten Variablen verunreinigt den Bereich des Aufrufers. Wenn Sie einen Wert zurückgeben wollen, können Sie den Namen einer Variablen an Ihre Funktion übergeben und dann den Befehl set
mit dem speziellen Argument PARENT_SCOPE
aufrufen:
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
In ähnlicher Weise definiert ein Paar von macro
/endmacro
Befehlen ein Makro. Im Gegensatz zu Funktionen laufen Makros im gleichen Bereich wie ihr Aufrufer. Daher werden alle Variablen, die innerhalb eines Makros definiert sind, im Bereich des Aufrufers gesetzt. Wir können die vorherige Funktion durch die folgende ersetzen:
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
Beide Funktionen und Makros akzeptieren eine beliebige Anzahl von Argumenten. Unbenannte Argumente werden der Funktion als Liste über eine spezielle Variable namens ARGN
übergeben. Hier ist eine Funktion, die jedes Argument, das sie empfängt, verdoppelt und jedes in einer separaten Zeile ausgibt:
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
Einschließlich anderer Skripte
CMake-Variablen werden im Bereich der Datei definiert. Der Befehl include
führt ein anderes CMake-Skript im gleichen Bereich wie das aufrufende Skript aus. Er ist ähnlich wie die #include
Direktive in C/C++. Sie wird typischerweise verwendet, um einen gemeinsamen Satz von Funktionen oder Makros im aufrufenden Skript zu definieren. Er verwendet die Variable CMAKE_MODULE_PATH
als Suchpfad.
Der Befehl find_package
sucht nach Skripten der Form Find*.cmake
und führt sie im gleichen Bereich aus. Solche Skripte werden oft verwendet, um externe Bibliotheken zu finden. Wenn sich beispielsweise eine Datei namens FindSDL2.cmake
im Suchpfad befindet, ist find_package(SDL2)
gleichbedeutend mit include(FindSDL2.cmake)
. (Beachten Sie, dass es mehrere Möglichkeiten gibt, den find_package
-Befehl zu verwenden – dies ist nur eine davon.)
CMake’s add_subdirectory
-Befehl hingegen erstellt einen neuen Bereich und führt dann das Skript namens CMakeLists.txt
aus dem angegebenen Verzeichnis in diesem neuen Bereich aus. Normalerweise verwenden Sie diesen Befehl, um dem aufrufenden Projekt ein weiteres CMake-basiertes Unterprojekt hinzuzufügen, z. B. eine Bibliothek oder eine ausführbare Datei. Die vom Unterprojekt definierten Ziele werden der Build-Pipeline hinzugefügt, sofern nicht anders angegeben. Keine der Variablen, die im Skript des Unterprojekts definiert sind, verschmutzen den Bereich des übergeordneten Projekts, es sei denn, die Option PARENT_SCOPE
des set
-Befehls wird verwendet.
Als Beispiel sind hier einige der Skripte aufgeführt, die beteiligt sind, wenn Sie CMake auf dem Turf-Projekt ausführen:
Eigenschaften erhalten und setzen
Ein CMake-Skript definiert Ziele mit den Befehlen add_executable
, add_library
oder add_custom_target
. Sobald ein Ziel erstellt ist, hat es Eigenschaften, die Sie mit den Befehlen get_property
und set_property
bearbeiten können. Im Gegensatz zu Variablen sind Ziele in jedem Bereich sichtbar, auch wenn sie in einem Unterverzeichnis definiert wurden. Alle Zieleigenschaften sind Zeichenketten.
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
Weitere Zieleigenschaften sind LINK_LIBRARIES
, INCLUDE_DIRECTORIES
und COMPILE_DEFINITIONS
. Diese Eigenschaften werden indirekt durch die Befehle target_link_libraries
, target_include_directories
und target_compile_definitions
geändert. Am Ende des Skripts verwendet CMake diese Zieleigenschaften, um die Build-Pipeline zu erstellen.
Es gibt auch Eigenschaften für andere CMake-Entitäten. Es gibt einen Satz von Verzeichniseigenschaften für jeden Dateibereich. Es gibt einen Satz globaler Eigenschaften, die von allen Skripten aus zugänglich sind. Und es gibt einen Satz von Quelldateieigenschaften für jede C/C++-Quelldatei.