Komplette CMake-Anleitung. Teil Zwei: Build System


Einführung


Dieser Artikel beschreibt die Verwendung des CMake-Build-Systems, das in einer Vielzahl von C / C ++ - Projekten verwendet wird. Es wird dringend empfohlen, den ersten Teil des Handbuchs zu lesen, um Missverständnisse der Syntax der CMake-Sprache zu vermeiden, die im gesamten Artikel explizit aufgeführt ist.


CMake-Start


Im Folgenden finden Sie Beispiele für die Verwendung der CMake-Sprache, die Sie üben sollten. Experimentieren Sie mit dem Quellcode, indem Sie vorhandene Befehle ändern und neue hinzufügen. Installieren Sie CMake von der offiziellen Website , um diese Beispiele auszuführen.


Arbeitsprinzip


Das CMake-Build-System ist ein Wrapper über andere plattformabhängige Dienstprogramme (z. B. Ninja oder Make ). Somit nimmt der Montageprozess selbst, egal wie paradox dies klingen mag, nicht direkt teil.


Das CMake-Build-System akzeptiert eine CMakeLists.txt Datei mit einer Beschreibung der Build-Regeln in der formalen CMake-Sprache und generiert dann Zwischen- und native Build-Dateien in demselben Verzeichnis, das auf Ihrer Plattform akzeptiert wird.


Generierte Dateien enthalten bestimmte Namen von Systemdienstprogrammen, Verzeichnissen und Compilern, während CMake-Befehle nur das abstrakte Konzept des Compilers verwenden und nicht an plattformabhängige Tools gebunden sind, die sich auf verschiedenen Betriebssystemen stark unterscheiden.


Überprüfen der CMake-Version


Der Befehl cmake_minimum_required überprüft die laufende Version von CMake: Wenn sie unter dem angegebenen Minimum liegt, wird CMake mit einem schwerwiegenden Fehler beendet. Ein Beispiel, das die typische Verwendung dieses Befehls am Anfang einer CMake-Datei demonstriert:


 #     CMake: cmake_minimum_required(VERSION 3.0) 

Wie in den Kommentaren erwähnt, setzt der Befehl cmake_minimum_required alle Kompatibilitätsflags (siehe cmake_policy ). Einige Entwickler legen absichtlich eine niedrige Version von CMake fest und passen die Funktionalität dann manuell an. Auf diese Weise können Sie gleichzeitig die alten Versionen von CMake unterstützen und an einigen Stellen neue Funktionen nutzen.


Projektdesign


Zu Beginn jeder CMakeLists.txt die Projektmerkmale mit dem Projektteam angeben, um ein besseres Design mit integrierten Umgebungen und anderen Entwicklungstools zu erzielen.


 #    "MyProject": project(MyProject VERSION 1.2.3.4 LANGUAGES C CXX) 

Es ist erwähnenswert, dass die Standardsprachen C CXX sind, wenn das Schlüsselwort LANGUAGES weggelassen wird. Sie können die Angabe von Sprachen auch deaktivieren, indem Sie das Schlüsselwort NONE als Liste von Sprachen schreiben oder einfach eine leere Liste hinterlassen.


Ausführen von Skriptdateien


Der Befehl include ersetzt die Zeile seines Aufrufs durch den Code der angegebenen Datei und verhält sich ähnlich wie der Befehl include des C / C ++ - Präprozessors include . In diesem Beispiel wird die Skriptdatei MyCMakeScript.cmake beschriebenen Befehl ausgeführt:


 message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") #   `MyCMakeScript.cmake`  : include(MyCMakeScript.cmake) message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") 

In diesem Beispiel benachrichtigt die erste Nachricht, dass die Variable TEST_VARIABLE noch nicht definiert wurde. Wenn das Skript MyCMakeScript.cmake diese Variable MyCMakeScript.cmake , informiert die zweite Nachricht bereits über den neuen Wert der MyCMakeScript.cmake . Daher erstellt die im Befehl include enthaltene Skriptdatei keinen eigenen Bereich, der in den Kommentaren zum vorherigen Artikel erwähnt wurde .


Zusammenstellung ausführbarer Dateien


Der Befehl add_executable kompiliert die ausführbare Datei mit dem angegebenen Namen aus der add_executable . Es ist wichtig zu beachten, dass der endgültige Dateiname von der Zielplattform abhängt (z. B. <ExecutableName>.exe oder nur <ExecutableName> ). Ein typisches Beispiel für das Aufrufen dieses Befehls:


 #    "MyExecutable"  #  "ObjectHandler.c", "TimeManager.c"  "MessageGenerator.c": add_executable(MyExecutable ObjectHandler.c TimeManager.c MessageGenerator.c) 

Bibliothekszusammenstellung


Der Befehl add_library kompiliert die Bibliothek mit der angegebenen Ansicht und dem angegebenen Namen aus der Quelle. Es ist wichtig zu beachten, dass der endgültige Bibliotheksname von der Zielplattform abhängt (z. B. lib<LibraryName>.a oder <LibraryName>.lib ). Ein typisches Beispiel für das Aufrufen dieses Befehls:


 #    "MyLibrary"  #  "ObjectHandler.c", "TimeManager.c"  "MessageConsumer.c": add_library(MyLibrary STATIC ObjectHandler.c TimeManager.c MessageConsumer.c) 

  • Statische Bibliotheken werden durch das Schlüsselwort STATIC als zweites Argument definiert und sind Archive von Objektdateien, die ausführbaren Dateien und anderen Bibliotheken zur Kompilierungszeit zugeordnet sind.
  • Dynamische Bibliotheken werden durch das Schlüsselwort SHARED als zweites Argument angegeben und sind SHARED die vom Betriebssystem während der Programmausführung geladen werden.
  • Modulare Bibliotheken werden durch das Schlüsselwort MODULE als zweites Argument definiert und sind MODULE die mithilfe der Ausführungstechnik von der ausführbaren Datei selbst geladen werden.
  • Objektbibliotheken werden durch das Schlüsselwort OBJECT als zweites Argument definiert und sind eine Reihe von Objektdateien, die ausführbaren Dateien und anderen Bibliotheken zur Kompilierungszeit zugeordnet sind.

Quelle zum Ziel hinzufügen


Es gibt Fälle, in denen mehrere Quelldateien zum Ziel hinzugefügt werden müssen. Zu diesem target_sources Befehl target_sources , mit dem dem Ziel viele Male Quellen target_sources können.


Das erste Argument für den Befehl target_sources ist der Name des Ziels, das zuvor mit den add_executable add_library oder add_executable wurde. Die nachfolgenden Argumente sind eine Liste der hinzuzufügenden Quelldateien.


Wiederholte Aufrufe des target_sources fügen die Quelldateien dem Ziel in der Reihenfolge hinzu, in der sie aufgerufen wurden, sodass die beiden unteren Codeblöcke funktional äquivalent sind:


 #    "MyExecutable"   # "ObjectPrinter.c"  "SystemEvaluator.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c) #    "MyExecutable"  "MessageConsumer.c": target_sources(MyExecutable MessageConsumer.c) #    "MyExecutable"  "ResultHandler.c": target_sources(MyExecutable ResultHandler.c) 

 #    "MyExecutable"   # "ObjectPrinter.c", "SystemEvaluator.c", "MessageConsumer.c"  "ResultHandler.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c MessageConsumer.c ResultHandler.c) 

Generierte Dateien


Der Speicherort der Ausgabedateien, die mit den add_library add_executable und add_library generiert wurden, wird erst in der Generierungsphase festgelegt. Diese Regel kann jedoch mit mehreren Variablen geändert werden, die den endgültigen Speicherort der Binärdateien bestimmen:



Ausführbare Dateien gelten immer als Ausführungsziele, statische Bibliotheken als Archivierungsziele und modulare Bibliotheken als Bibliotheksziele. Bei "Nicht-DLL" -Plattformen werden dynamische Bibliotheken als Bibliotheksziele und bei "DLL-Plattformen" als Ausführungsziele betrachtet. Solche Variablen werden für Objektbibliotheken nicht bereitgestellt, da diese Art von Bibliotheken im Darm des CMakeFiles Verzeichnisses CMakeFiles .


Es ist wichtig zu beachten, dass alle Windows-basierten Plattformen, einschließlich Cygwin, als "DLL-Plattformen" betrachtet werden.


Bibliothekslayout


Der Befehl target_link_libraries Bibliothek oder ausführbare Datei mit anderen bereitgestellten Bibliotheken. Das erste Argument für diesen Befehl ist der Name des Ziels, das durch die add_library add_executable oder add_library generiert wurde, und die nachfolgenden Argumente sind die Namen der Bibliotheksziele oder die vollständigen Pfade zu den Bibliotheken. Ein Beispiel:


 #    "MyExecutable"  #  "JsonParser", "SocketFactory"  "BrowserInvoker": target_link_libraries(MyExecutable JsonParser SocketFactory BrowserInvoker) 

Es ist anzumerken, dass modulare Bibliotheken nicht mit ausführbaren Dateien oder anderen Bibliotheken verknüpft werden können, da sie ausschließlich zum Laden durch Ausführungstechniken bestimmt sind.


Mit Zielen arbeiten


Wie in den Kommentaren erwähnt, unterliegen Ziele in CMake ebenfalls einer manuellen Manipulation, sind jedoch sehr begrenzt.


Es ist möglich, die Eigenschaften von Zielen zu steuern, mit denen der Projektzusammenstellungsprozess festgelegt werden soll. Der Befehl get_target_property Wert der get_target_property angegebene Variable. In diesem Beispiel wird der Wert der C_STANDARD Eigenschaft des C_STANDARD Ziels auf dem Bildschirm angezeigt:


 #   "VALUE"   "C_STANDARD": get_target_property(VALUE MyTarget C_STANDARD) #      : message("'C_STANDARD' property is equal to [${VALUE}]") 

Der Befehl set_target_properties setzt die angegebenen set_target_properties auf die angegebenen Werte. Dieser Befehl akzeptiert eine Liste von Zielen, für die Eigenschaftswerte festgelegt werden, und anschließend das Schlüsselwort PROPERTIES , gefolgt von einer Liste der Form < > < > :


 #   'C_STANDARD'  "11", #   'C_STANDARD_REQUIRED'  "ON": set_target_properties(MyTarget PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON) 

Im obigen Beispiel werden die Eigenschaften der MyTarget Ziele festgelegt, die sich auf den Kompilierungsprozess auswirken: Beim Kompilieren des MyTarget Ziels MyTarget Compiler für CMake den C11-Standard verwenden. Alle bekannten Namen von Zieleigenschaften werden auf dieser Seite aufgelistet.


Es ist auch möglich, zuvor definierte Ziele mit dem Konstrukt if(TARGET <TargetName>) zu überprüfen:


 #  "The target was defined!"   "MyTarget"  , #    "The target was not defined!": if(TARGET MyTarget) message("The target was defined!") else() message("The target was not defined!") endif() 

Unterprojekte hinzufügen


Der Befehl add_subdirectory fordert CMake auf, die angegebene Unterprojektdatei sofort zu verarbeiten. Das folgende Beispiel zeigt die Anwendung des beschriebenen Mechanismus:


 #   "subLibrary"    , #       "subLibrary/build": add_subdirectory(subLibrary subLibrary/build) 

In diesem Beispiel ist das erste Argument für den Befehl add_subdirectory Unterprojekt add_subdirectory Das zweite Argument ist optional und informiert CMake über den Ordner, der für die generierten Dateien des enthaltenen Unterprojekts vorgesehen ist (z. B. CMakeCache.txt und cmake_install.cmake ).


Es ist zu beachten, dass alle Variablen aus dem übergeordneten Bereich vom hinzugefügten Verzeichnis geerbt werden und alle in diesem Verzeichnis definierten und neu definierten Variablen nur für dieses sichtbar sind (wenn das Schlüsselwort PARENT_SCOPE nicht durch das Befehlsargument set angegeben wurde). Diese Funktion wurde in den Kommentaren zum vorherigen Artikel erwähnt .


Paketsuche


Der Befehl find_package findet und lädt die Einstellungen eines externen Projekts. In den meisten Fällen wird es für die spätere Verknüpfung externer Bibliotheken wie Boost und GSL verwendet . In diesem Beispiel wird der beschriebene Befehl aufgerufen , um nach der GSL- Bibliothek zu suchen und dann zu verknüpfen:


 #     "GSL": find_package(GSL 2.5 REQUIRED) #      "GSL": target_link_libraries(MyExecutable GSL::gsl) #      "GSL": target_include_directories(MyExecutable ${GSL_INCLUDE_DIRS}) 

Im obigen Beispiel akzeptiert der Befehl find_package den Namen des Pakets als erstes Argument und dann die erforderliche Version. Die Option REQUIRED erfordert das Drucken eines schwerwiegenden Fehlers und das Beenden von CMake, wenn das erforderliche Paket nicht gefunden wird. Das Gegenteil ist die QUIET Option, bei der CMake seine Arbeit fortsetzen muss, auch wenn das Paket nicht gefunden wurde.


Als Nächstes wird die MyExecutable mit dem Befehl target_link_libraries mithilfe der Variablen GSL::gsl mit der GSL-Bibliothek GSL::gsl , die den Speicherort der bereits kompilierten GSL kapselt.


Am Ende wird der Befehl target_include_directories , der den Compiler über den Speicherort der GSL-Bibliotheksheaderdateien informiert. Bitte beachten Sie, dass die Variable GSL_INCLUDE_DIRS verwendet wird, GSL_INCLUDE_DIRS von mir beschriebenen Header zu GSL_INCLUDE_DIRS (dies ist ein Beispiel für importierte Paketeinstellungen).


Sie möchten wahrscheinlich das Ergebnis einer QUIET überprüfen, wenn Sie die Option QUIET angegeben haben. Dies kann durch Überprüfen der <PackageName>_FOUND , die nach Abschluss des find_package automatisch ermittelt wird. Wenn Sie beispielsweise erfolgreich GSL-Einstellungen in Ihr Projekt importieren, wird die Variable GSL_FOUND wahr.


Im Allgemeinen hat der Befehl find_package zwei find_package des Starts: modular und Konfiguration. Im obigen Beispiel wurde eine modulare Form angewendet. Dies bedeutet, dass CMake beim Find<PackageName>.cmake des CMAKE_MODULE_PATH Verzeichnis CMAKE_MODULE_PATH nach einer Skriptdatei der Form Find<PackageName>.cmake CMAKE_MODULE_PATH , diese dann startet und alle erforderlichen Einstellungen importiert (in diesem Fall hat CMake die Standarddatei FindGSL.cmake gestartet).


Möglichkeiten, Header einzuschließen


Sie können den Compiler mit zwei Befehlen über den Speicherort der enthaltenen Header informieren: include_directories und target_include_directories . Sie entscheiden, welche Sie verwenden möchten. Es lohnt sich jedoch, einige Unterschiede zwischen ihnen zu berücksichtigen (die Idee wird in den Kommentaren vorgeschlagen ).


Der Befehl include_directories wirkt sich auf den Bereich des Verzeichnisses aus. Dies bedeutet, dass alle durch diesen Befehl angegebenen Header-Verzeichnisse für alle Zwecke der aktuellen CMakeLists.txt sowie für verarbeitete Teilprojekte verwendet werden (siehe add_subdirectory ).


Der Befehl target_include_directories wirkt sich target_include_directories auf das im ersten Argument angegebene Ziel aus und nicht auf andere Ziele. Das folgende Beispiel zeigt den Unterschied zwischen den beiden Befehlen:


 add_executable(RequestGenerator RequestGenerator.c) add_executable(ResponseGenerator ResponseGenerator.c) #     "RequestGenerator": target_include_directories(RequestGenerator headers/specific) #    "RequestGenerator"  "ResponseGenerator": include_directories(headers) 

In den Kommentaren wird erwähnt, dass in modernen Projekten die Verwendung der link_libraries include_directories und link_libraries unerwünscht ist. Eine Alternative sind die target_link_libraries target_include_directories und target_link_libraries , die nur auf bestimmte Ziele und nicht auf den gesamten aktuellen Bereich target_link_libraries .


Projektinstallation


Der Befehl install generiert Installationsregeln für Ihr Projekt. Dieser Befehl kann mit Zielen, Dateien, Ordnern und mehr arbeiten. Betrachten Sie zunächst das Setzen von Zielen.


Um Ziele festzulegen, müssen Sie das Schlüsselwort TARGETS als erstes Argument der beschriebenen Funktion übergeben, gefolgt von einer Liste der festzulegenden Ziele und dem Schlüsselwort DESTINATION mit dem Speicherort des Verzeichnisses, in dem die angegebenen Ziele festgelegt werden. Dieses Beispiel zeigt eine typische Zielsetzung:


 #   "TimePrinter"  "DataScanner"   "bin": install(TARGETS TimePrinter DataScanner DESTINATION bin) 

Der Prozess zum Beschreiben der Installation von Dateien ist ähnlich, außer dass TARGETS anstelle des Schlüsselworts TARGETS angeben müssen. Ein Beispiel für die Installation von Dateien:


 #   "DataCache.txt"  "MessageLog.txt"   "~/": install(FILES DataCache.txt MessageLog.txt DESTINATION ~/) 

Der Prozess zum Beschreiben der Installation von Ordnern ist ähnlich, außer dass Sie DIRECTORY anstelle des Schlüsselworts FILES angeben müssen. Es ist wichtig zu beachten, dass während der Installation der gesamte Inhalt des Ordners kopiert wird und nicht nur sein Name. Ein Beispiel für die Installation von Ordnern lautet wie folgt:


 #   "MessageCollection"  "CoreFiles"   "~/": install(DIRECTORY MessageCollection CoreFiles DESTINATION ~/) 

Nachdem Sie die CMake-Verarbeitung aller Ihrer Dateien abgeschlossen haben, können Sie alle beschriebenen Objekte mit dem sudo checkinstall installieren (wenn CMake ein Makefile generiert), oder Sie können diese Aktion mit der integrierten Entwicklungsumgebung ausführen, die CMake unterstützt.


Visuelles Beispiel des Projekts


Dieses Handbuch wäre nicht vollständig, ohne ein reales Beispiel für die Verwendung des CMake-Build-Systems zu demonstrieren. Betrachten Sie ein einfaches Projektdiagramm mit CMake als einzigem Build-System:


 + MyProject - CMakeLists.txt - Defines.h - StartProgram.c + core - CMakeLists.txt - Core.h - ProcessInvoker.c - SystemManager.c 

Die CMakeLists.txt Datei CMakeLists.txt beschreibt die Kompilierung des gesamten Programms: Zuerst wird der Befehl add_executable , der die ausführbare Datei kompiliert, dann der Befehl add_subdirectory , der die Verarbeitung des Teilprojekts anregt, und schließlich wird die ausführbare Datei mit der kompilierten Bibliothek verknüpft:


 #    CMake: cmake_minimum_required(VERSION 3.0) #   : project(MyProgram VERSION 1.0.0 LANGUAGES C) #      "MyProgram": add_executable(MyProgram StartProgram.c) #    "core/CMakeFiles.txt": add_subdirectory(core) #    "MyProgram"  #    "MyProgramCore": target_link_libraries(MyProgram MyProgramCore) #    "MyProgram"   "bin": install(TARGETS MyProgram DESTINATION bin) 

Die Datei core/CMakeLists.txt wird von der core/CMakeLists.txt aufgerufen und kompiliert die statische Bibliothek MyProgramCore die für die Verknüpfung mit der ausführbaren Datei vorgesehen ist:


 #    CMake: cmake_minimum_required(VERSION 3.0) #      "MyProgramCore": add_library(MyProgramCore STATIC ProcessInvoker.c SystemManager.c) 

Nach einer Reihe von cmake . && make && sudo checkinstall Befehlen cmake . && make && sudo checkinstall cmake . && make && sudo checkinstall CMake Build System wird erfolgreich abgeschlossen. Der erste Befehl beginnt mit der Verarbeitung der Datei CMakeLists.txt im Stammverzeichnis des Projekts, der zweite Befehl kompiliert schließlich die erforderlichen Binärdateien und der dritte Befehl installiert die kompilierte MyProgram im System.


Fazit


Jetzt können Sie Ihre eigenen CMake-Dateien schreiben und die CMake-Dateien anderer Personen verstehen. Weitere Informationen zu anderen Mechanismen finden Sie auf der offiziellen Website .


Der nächste Artikel in diesem Handbuch konzentriert sich auf das Testen und Erstellen von Paketen mit CMake und wird in einer Woche veröffentlicht. Bis bald!

Source: https://habr.com/ru/post/de432096/


All Articles