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.

Articles

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.