CMake

Allgemein

CMake ist ein plattformunabhängiges Tool zur Erstellung von Builddateien für unterschiedliche Systeme (Visual Studio, Eclipse, Make und andere). Des Weiteren bietet CMake diverse plattformunabhängige Dateioperationen (Kopieren, Entfernen, Verzeichnisse durchsuchen etc).

Anleitungen

Best Practices & Tipps

  • Es ist stets ein Out-of-Source Build anzustreben. Dies erleichtert die Verwendung verschiedener IDE's und Versionsverwaltungen.
  • Der CMake Cache führt manchmal zu Problemen, da Variablen falsch initialisiert werden. D.h. bei Problemen Cache löschen.
  • Die Einbindung von Bibliotheken hängt stark von der Qualität der Find*.cmake Dateien ab. Diese liegen im CMakeFiles Ordner der Bibliothek oder im Installationspfad von CMake z.B. FindVTK.cmake oder FindQt.cmake). Für Multi-Konfiguration Builds (z.B. Visual Studio) sind oft Anpassungen in diesen Dateien nötig.
  • TODO Ordnerstruktur (build, cmake, etc.)

Einführung

Der Build-Prozess wird mittels Dateien mit dem Namen CMakeLists.txt beschrieben. Diese werden üblicherweise im Hauptordner und den verschiedenen Unterodner des Projektes abgelegt.

Die nachfolgende Grafik zeigt grob die Verzeichnisse, welche in einer CMake-Umgebung vorhanden sind und deren wichtigste Inhalte. Aus dem Quellverzeichnis wird mittels CMake das Ausgabeverzeichnis (Binary dir) erzeugt. Es enthält die Werte der Konfiguration, sowie die Ergebnisse von Tests (z.B. Endianess) und Dateien für die Einbindung in andere CMake-Projekte. Letztere müssen jedoch durch den Benutzer erzeugt werden (siehe Export von Projekten). Durch die Installation wird die Software üblicherweise ins Zielsystem installiert (z.B. Ablage nach C:/Program Files/xyz unter Windows). Dieser Schritt kann jedoch auch dazu verwendet werden, um alle benötigten Zusatzbilbiotheken ins Zielverzeichnis zu kopieren. Die Installation enthält relative Pfade und ist somit unabhängig vom Zielsystem.

Konfiguration

Für die Konfiguration eines CMake-Projektes wird oft das CMake-GUI verwendet. Es wird der Pfad zur Haupt-CMakeLists.txt angegeben und ein Ordner, welcher die Dateien für den Build enthält. Im Configure Schritt wird der Compiler festgelegt und die Umgebung analysiert. Neue Variablen werden rot eingefärbt und sollten überprüft werden. Configure wird solange ausgeführt, bis keine Variablen mehr rot sind. Der Aufruf von Generate erzeugt die entsprechenden Build-Dateien (Solutions etc.).

Sprachkonstrukte

Kommando

<Kommando-Name>(Argumente)

Leerzeichengetrennte Argumente werden als Liste interpretiert. Ansonsten können Anführungszeichen verwendet werden.

Variablen

set(<Variable> Argumente)

Variablen können über ${Variablenname} abgerufen werden. Umgebungsvariablen können via $ENV{Variablenname} und die Windows-Registry über deren Keys HKEY_... angesprochen werden. Eine Variable ist im aktuellen Ordner sowie allen Unterordnern sichtbar. In Unterordner kann eine Variable jedoch nur durch Angabe von PARENT_SCOPE geändert werden. Variablen können auch mit Variablen definiert werden z.B. ${${Name}_DIR}_FLAG.

Bedingung

if(Ausdruck)
elseif()
else()
endif()

Als Ausdruck können logische Verknüpfungen von Variablen, aber auch String und Integervergleiche und Verzeichnisabfragen eingesetzt werden. Detaillierte Informationen siehe CMake Documentation Command if

Schleifen

foreach(Variable Liste)
endforeach()
while(Ausdruck)
endwhile()

Funktionen & Makros

function(Funktionsname Argumente)
endfunction()
macro(Funktionsname Argumente)
endmacro()

Funktionen haben einen eigenen Variablen-Scope, wohingegen Makros nur eine Textersetzung darstellen.

Policies

Um die Kompatibilität mit älteren CMake-Versionen sicherzustellen sind verschiedene Policies vorhanden. Wird das Verhalten einer Funktion geändert, so wird dazu in neueren Versionen eine Policy eingeführt, welche standardmässig das alte Verhalten anwendet und eine Warnung ausgibt. Policies können einzeln angepasst oder durch Definition der Minimal-Version

 cmake_minimum_required(VERSION <Versionsnummer>) 

global gesetzt werden.

Spezielle Funktionen

Präprozessormakros

Für die plattformunabhängige Übergabe von Direktiven ist das Kommando

 add_definitions(-D<Direktive>) 

vorgesehen.

Configure-Files

Mit Hilfe von CMake können Dateien aus Vorlagen und aktuellen Variablendefinitionen erzeugt werden. Der Inhalt der Vorlagedatei wird dabei kopiert und die Platzhalter mit Variableninhalt gefüllt.

configure_file(
  ${PROJECT_SOURCE_DIR}/template.in 
  ${PROJECT_BINARY_DIR}/template.out
  )
template.in
/* Insert a #define if the Variable is set */
cmakedefine <Variablename>
/* Insert the contents of a Variable */
String userFolder = ${USER_FOLDER}

Export von Projekten

Um CMake-Projekte in anderen zu referenzieren können die Buildinformationen exportiert werden. Weitere Informationen siehe Mastering CMake Kapitel 5.7 Creating CMake Package Configuration Files.

set(PACKAGE_NAME <Paketname>)
Set variable containing all include directories, used in configure file
get_target_property(${PACKAGE_NAME}_INCLUDE_DIRS <Target> INCLUDE_DIRECTORIES)
export(TARGETS <Targetliste> FILE ${PACKAGE_NAME}-targets.cmake)
configure_file( 
  ${${PROJECT_NAME}_SOURCE_DIR}/${PACKAGE_NAME}-config.cmake.in 
  ${${PROJECT_NAME}_BINARY_DIR}/${PACKAGE_NAME}-config.cmake 
)
 
set(VERSION <Versionsnummer>)
configure_file( 
  ${${PROJECT_NAME}_SOURCE_DIR}/${PACKAGE_NAME}-config-version.cmake.in 
  ${${PROJECT_NAME}_BINARY_DIR}/${PACKAGE_NAME}-config-version.cmake 
)
*-config.cmake.in
# Import the targets
include("@@PROJECT_NAME@_BINARY_DIR@/@PACKAGE_NAME@-targets.cmake")
 
# Report other information i.e include directories used
# Nested @ are not supported
set(@PROJECT_NAME@_INCLUDE_DIRS "@<Projektname>_INCLUDE_DIRS@") 
*-config-version.cmake.in
set(PACKAGE_VERSION "@version@")
if(NOT "${PACKAGE_FIND_VERSION}" VERSION_GREATER "@version@")
  set (PACKAGE_VERSION_COMPATIBLE 1)
  if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "@version@")
    set(PACKAGE_VERSION_EXACT 1)
  endif()
endif()

IMPORTANT Achtung statische Bibliotheken

Werden statische Bilbiotheken gelinkt, so enthält der Export zwar die Information der Abhängigkeit, jedoch nicht den Standort der Bibliotheken. Um dies zu beheben, wird im *-config File ein find_package für die statische Bibliothek eingefügt.

Install Scripts

Um ein Projekt plattformunabhängig zu installieren (z.B. nach C:/Program Files/…) bietet CMake das install Kommando. Die Details zur nachstehenden Syntax sind Mastering CMake zu entnehmen.

install(
  TARGETS <Targetlist>
  RUNTIME DESTINATION bin COMPONENT Runtime
  LIBRARY DESTINATION lib COMPONENT Runtime
  ARCHIVE DESTINATION lib/<projectname> COMPONENT Development
)

Drittbibliotheken

Damit Drittbibliotheken nicht von Hand in den Output-Ordner kopiert werden müssen (z.B. für Visual Studio) sind die BundleUtilities vorhanden. Dazu wird das Target zuerst installiert siehe Install Scripts. Über die Anwendung (oder Bibliothek) wird nach abhängigen Bibliotheken gesucht.

configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/FixBundle.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake
@ONLY
)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake)
FixBundle.cmake.in
include(BundleUtilities)
 
# Set bundle to the full path name of the executables already existing 
set(bundle "${CMAKE_INSTALL_PREFIX}/@PROJECT_NAME@@CMAKE_EXECUTABLE_SUFFIX@")
 
# Set other_libs to a list of full path names to additionale libraries that cannot be reached by dependency analysis (i.e dynamically loaded plugins)
set(other_libs "")
 
# Set dirs to a list of directories where prerequisite libraries may be found
set(dirs "@LIBRARY_OUTPUT_PATH@")
 
fixup_bundle("${bundle}" "${other_libs}" "${dirs}")

Literatur

Ken Martin, Bill Hoffman: Mastering CMake. ISBN 978-1930934221.