Variablen setzen und Quelldateien finden

Bislang war unser CMake-Projekt sehr klein und wenig hilfreich. Spannender wird CMake aber vor allem beim Bauen gegen externe Bibliotheken, wenn Projekte konfiguriert werden können oder wenn die Ordnerstruktur komplizierter wird. Normale GNU-Makefiles arbeiten mit eingebauten String- und Dateifunktionen oder mit Shell-Zugriff, CMake hat eigene eingebaute Funktionen um Dateien in Ordnern zu finden. Anders als GNU-Makefiles kann CMake selbst Bibliotheken im System aufspüren und auch Headerpfade richtig setzen.

Variablen

Aber eins nach dem Anderen. Zunächst wollen wir ein paar CMake-Variablen setzen und benutzen. Einige Variablen kennt CMake selbst, bspw. den C-Standard, den wir setzen können. Um den C-Standard auf C99 zu setzen, verwenden wir folgende Befehle:

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED True)

CMake kennt eine ganze Reihe von Variablen, die entweder Informationen bringen oder aber CMake Informationen mitteilen können. Eine Auflistung dieser Variablen ist im CMake-Handbuch zu finden.

Mit set werden aber auch Variablen gesetzt, die wir in unserem CMakefile weiter verwenden wollen. Das benutzen wir bspw. wenn wir Ordnernamen ein einziges Mal eingeben wollen. Der set-Befehl könnte dann so aussehen:

set(GIT_TAG "")

Gesetzt Variablen werden, ähnlich wie Variablen (manchmal) in Bash mit ${…} verwendet, also beispielsweise:

string(STRIP "${GIT_TAG}" GIT_TAG)

Der String Befehl erlaubt eine Reihe von String-Operationen, also Manipulation und Finden von und innerhalb von Zeichenketten. Weitere Informationen sind im CMake-Handbuch zu finden.

if-then-else

Variablen werden oftmals verwendet um in bestimmten Befehlen gesetzt zu werden und dann geprüft zu werden. Mit if-then-else finden wir heraus, ob eine Bedingung stimmt und führen Befehle aus, falls das zutrifft. Wichtig zu beachten ist, dass CMake verlangt, dass auch das endif ein Klammerpaar besitzt und Leerzeichen zwischen Befehl und Klammern nicht erlaubt. if-then-else nimmt in CMake folgende Form an:

if(A)
    # do A
elseif(B)
    # do B
else()
    # do something else
endif()

Die Bedingungen sind vielfältig, beispielsweise können wir prüfen ob Variablen gesetzt sind mit DEFINED, ob Strings identisch sind mit STREQUAL oder ob Dateien existieren mit EXISTS. Das folgende Beispiel könnte eine Versionserkennung sein:

set(GIT_TAG "")
set(VERSION "")
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/version)
    file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version VERSION)
    message("-- VERSION from file: ${VERSION}")
elseif(NOT ${GIT_TAG} STREQUAL "")
    set(VERSION "v${GIT_TAG}")
    message("-- VERSION git tag: ${VERSION}")
elseif(DEFINED $ENV{PKG_VERSION})
    set(VERSION "${ENV{PKG_VERSION}")
    message("-- VERSION PKG Version: ${VERSION}")
else()
    set(VERSION "unknown")
    message("-- VERSION unknown version")
endif()

Zu beachten am Beispiel ist, dass die Umgebungsvariable PKG_VERSION von der Umgebung gesetzt sein könnte, und GIT_TAG muss von uns weiter oben vorbelegt werden. Sollte auch die Datei version im aktuellen Source-Verzeichnis fehlen, erhalten wir hier eine unbekannte Versionsangabe.

Quelldateien finden

Spannend wird CMake aber vor allem dann, da es sich aus verschiedenen Verzeichnissen Quelldateien zusammensammeln kann und zu einer Binary bauen kann. Mit dem file-Befehl geben wir CMake die Aufgabe Datein in einem Verzeichnis zu suchen. Oftmals wird der Befehl zu benutzt:

# Syntax: file(GLOB_RECURSE <variable> 
#                 [FOLLOW_SYMLINKS] [LIST_DIRECTORIES true|false] 
#                 [RELATIVE <path>] [CONFIGURE_DEPENDS] 
#                 [<globbing-expressions>...])
 
file(GLOB_RECURSE SRC_FILES "*.c")

Hier geben wir den Unterbefehl GLOB_RECURSE an, zusammen mit der Zielvariable SRC_FILES und dem Globbing-String „*.c“. GLOB_RECURSE gibt an, dass CMake in diesem Verzeichnis und allen Unterverzeichnissen (rekursiv) nach Dateien suchen soll, die einem Globbing-String entsprechen. Ein Globbing-String ist in etwa wie eine Schablone. Wir geben *.c an; das Sternchen dient als Platzhalter für beliebige Zeichen, also suchen wir nach allen Dateien, die auf .c enden.

Neben GLOB_RECURSE gibt es noch andere Unterbefehle, bspw. GLOB, weitere Informationen zu den Befehlen gibt es im CMake-Handbuch.

file legt nun aber alle Dateien im aktuellen Verzeichnis in der Variable SRC_FILES ab. Diese Variable können wir nutzen um unser Programm zu bauen, bspw. so:

add_executable(hello_world ${SRC_FILES})

Egal wie viele Dateien CMake gefunden hat, es wird nun hier alle übersetzen und zur Programmdatei hello_world zusammenlinken.