CMakeLists.txt

CMake ist ein Build-System für Build-Systeme, d.h. es erstellt Konfigurationsdatein für andere Build-Systeme. Der Aufbau der CMake-Datein selbst folgt dabei einer eigenen Syntax. Hier ein Beispiel:

cmake_minimum_required(VERSION 3.10)
project(modules LANGUAGES C VERSION 1.0.0)

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")

find_package(LibEV REQUIRED)

set(
        SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/main.c
        ${CMAKE_CURRENT_SOURCE_DIR}/bun.c
        ${CMAKE_CURRENT_SOURCE_DIR}/topping.c
        ${CMAKE_CURRENT_SOURCE_DIR}/patty.c
)

add_executable(${PROJECT_NAME} ${SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR} ${LIBEV_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBEV_LIBRARIES})

CMake Definitionen müssen in einer Daten namens "CMakeLists.txt" gespeichert sein. Dieser Name ist aus traditionellen Gründen vorgegeben. Befehle in CMake sind case-insensitive, heißt: Groß- und Kleinschreibung spielt keine Rolle. Dies gilt allerdings nicht für die Parameter der Befehle!

Jedes CMake-File beginnt mit dem Befehl cmake_minimum_required(VERSION X) wobei X für eine CMake-Version steht. Der Befehl stellt sicher, dass das ausführende CMake alle verwendeten Befehle kennt. Die nächste Zeile konfiguriert das zu bauende Projekt. project() nimmt als ersten Parameter den Namen der Software an, der Parameter LANGUAGES C stellt ein, dass es sich um ein C-Projekt handelt, und VERSION 1.0.0 setzt die Versionsnummer des Projekts auf 1.0.0.

Die Befehle:

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")

Erzwingen die Verwendung des C11-Standards während der Compilierung und setzten notwendige Compiler-Flags.

Der Set-Befehl auf SOURCES listet alle C-Dateien auf, die compiliert werden sollen. add_executable() weist CMake an ein ausführbares Binary zu bauen. Dieses trägt den in project() definierten Namen (Variable ${PROJECT_NAME}) und wird aus den Dateien in ${SOURCES} erstellt. Diese Definition erzeugt weiterhin ein sogenanntes "Target" mit gleichem Namen. Das Target dient dazu, Konfigurationen für das Binary zu setzten. Dabei muss immer der Target-Name angegeben werden. Dies ist gut in den letzten beiden Zeilen zu sehen: target_include_directories() setzt die Pfade, an denen der Compiler nach Header-Dateien suchen soll. target_link_libraries() setzt die zu linkenden Funktionsbibliotheken.

Funktionsbibliotheken finden.

Zum Auffinden von Funktionsbibliotheken im System verwendet CMake den Befehl find_package(). Dieser nimmt den Namen der zu findenden Bibliothek an und liefert die Suchergebnisse in Form von Variablen zurück. Diese heißen, für Bibliothek "XYZ" typischerweise XYZ_INCLUDE_DIR (Ordner mit Header-Dateien) und XYZ_LIBRARIES (Pfade zur Funktionsbibliothek). Das Auffinden selbst übernehmen "Find-Scripte", die mit CMake ausgeliefert werden. Die Zeile set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") setzt einen zusätzlichen Pfad, in dem CMake nach Find-Scripten sucht. So können eigene Find-Scripte integriert werden. Das im Beispiel verwende Find-Script für die LibEv sieht wie folgt aus:

include(FindPackageHandleStandardArgs)

find_library(LIBEV_LIBRARIES NAMES ev ev4)
find_path(LIBEV_INCLUDE_DIR ev.h)

find_package_handle_standard_args(LibEV REQUIRED_VARS LIBEV_LIBRARIES LIBEV_INCLUDE_DIR)
mark_as_advanced(LIBEV_LIBRARIES LIBEV_INCLUDE_DIR)

Beschreibung:

  • include(FindPackageHandleStandardArgs) läd Code zur Verwendung des Befehls find_package_handle_standard_args
  • find_libary() sucht die entsprechende Bibliothek im System und legt den Speicherort in einer Variable ab
  • find_path() sucht eine Datei und speichert den Ordner-Pfad der Datei in einer Variable
  • find_package_handle_standard_args() prüft, ob alle notwendigen Variablen gesetzt sind (Datein gefunden wurden). Im Fehlerfall erzeugt CMake eine aussagekräftige Fehlermeldung.
  • mark_as_advanced(LIBEV_LIBRARIES LIBEV_INCLUDE_DIR) markiert die übergebenen Variablen als CMake-Interne Variablen.

CMake ausführen

Mit CMake führt man typischerweise sogenannte "Out-of-Source-Builds" aus, d.h. dass die temporären Dateien, die während der Compilierung erzeugt werden, in einem extra Ordner verschwinden. Dies ist besonders wichtig, wenn der Source-Code mit einem Versionsverwaltungsprogramm wie Git verwaltet wird. "In-Source-Builds", bei denen die temporären Dateien neben den Sourcen liegen, würden hier zu massiven Beeinträchtigungen bei der Verwaltung führen. Bei "Out-of-Source-Builds" kann der entsprechende Ordner einfach ignoriert oder gelöscht werden, ohne das dies Einfluss auf den Source-Code hat.

Der CMake-Aufruf ist denkbar einfach. CMake verwendet für alle nicht gesetzten Optionen sinnvolle Default-Werte, sodass nur eine echte Option verbleibt: Der Pfad zur "CMakeLists.txt". Ein CMake-Aufruf sieht daher typischerweise wie folgt aus:

# erzeuge einen Build-Ordner
$> mkdir build
# wechsle in den Build-Ornder
$> cd build
# rufe CMake auf
$> cmake /pfad/zur/CMakeLists.txt

Standardmäßig generiert CMake Unix Makefiles. Die Software lässt sich demnach durch einen Aufruf von $> make erstellen.

Sollte ein anderes Build-System als Unix Makefiles gewünscht sein, so kann dies CMake über eine Option mitgeteilt werden. so erstellt cmake -G"Ninja" /pfad/zur/CMakeLists.txt statt einem Makefile eine Datei "ninja.rules". Die Compilierung der Software erfolgt dann mit Ninja-Build via $> ninja.

Weitere Optionen

CMake kann noch deutlich mehr, als die hier beschriebenen Funktionen. Ein Blick auf cmake.org loht sich daher sehr. Hier eine kleine Liste, welche Optionen CMake bietet:

  • Bauen von Software aus dem Source-Code mit verschiedenen Build-Systemen
  • Patchen von Source-Code-Dateien vor der Compilierung
  • Code-Generierung über spezielle Werkzeuge
  • Ausführen von Unit-Tests
  • Verpacken von gebauter Software in Installationspacketen
  • Cross-Compilieren von Software für andere Systemarchitekturen
Last modified: Friday, 15 June 2018, 4:15 PM