Wie wird CMake verwendet? [geschlossen]

95

Es ist bekanntermaßen schwierig, als Anfänger nützliche Informationen über CMake zu erhalten. Bisher habe ich einige Tutorials zum Einrichten eines sehr einfachen Projekts gesehen. Keines davon erklärt jedoch die Gründe für alles , was in ihnen gezeigt wird, und lässt immer viele Löcher zu füllen.

Was bedeutet Aufruf CMake auf einem CMakeLists bedeuten ? Soll es einmal pro Build-Baum aufgerufen werden oder was? Wie verwende ich unterschiedliche Einstellungen für jeden Build, wenn alle dieselbe CMakeLists.txt-Datei aus derselben Quelle verwenden? Warum benötigt jedes Unterverzeichnis eine eigene CMakeLists-Datei? Wäre es sinnvoll, CMake in einer anderen CMakeLists.txt als der im Stammverzeichnis des Projekts zu verwenden? Wenn ja, in welchen Fällen? Was ist der Unterschied zwischen der Angabe, wie eine ausführbare Datei oder Bibliothek aus der CMakeLists-Datei in ihrem eigenen Unterverzeichnis erstellt wird, und der Angabe in der CMakeLists-Datei im Stammverzeichnis aller Quellen? Kann ich ein Projekt für Eclipse und ein anderes für Visual Studio erstellen und dabei die -GOption beim Aufrufen von CMake ändern ? Wird es überhaupt so verwendet?

Keines der Tutorials, Dokumentationsseiten oder Fragen / Antworten, die ich bisher gesehen habe, gibt nützliche Einblicke in das Verständnis der Verwendung von CMake. Die Beispiele sind einfach nicht gründlich. Egal welche Tutorials ich lese, ich habe das Gefühl, dass mir etwas Wichtiges fehlt.

Es gibt viele Fragen von CMake-Neulingen wie mir, die dies nicht explizit stellen, aber die Tatsache deutlich machen, dass wir als Neulinge keine Ahnung haben, wie wir mit CMake umgehen sollen oder was wir daraus machen sollen.

leinaD_natipaC
quelle
8
Vielleicht solltest du stattdessen einen Blog-Beitrag schreiben.
Steveve
2
Ich bin versucht, "zu breit" abzustimmen ... Dies fühlt sich eher wie ein persönlicher Aufsatz über CMake an als wie eine gute Lösung für den Stackoverflow. Warum haben Sie nicht alle diese Fragen in separate Fragen gestellt? Und wie unterscheidet sich Ihre Frage + Antwort von vielen anderen, z. B. stackoverflow.com/q/20884380/417197 , stackoverflow.com/q/4745996/417197 , stackoverflow.com/q/4506193/417197 ?
André
13
@Andre Warum diese Frage: Ich habe eine Woche lang versucht, CMake zu verstehen, und kein Tutorial, das ich gefunden habe, war vollständig. Die Links, auf die Sie hingewiesen haben, sind gut, aber für jemanden, der CMake gefunden hat, weil er ein Projekt mit einer Reihe von CMakeLists erhalten hat, werfen sie nicht viel Licht auf die Funktionsweise von CMake. Ich habe ehrlich gesagt versucht, eine Antwort zu schreiben, die ich mir wünschte, ich könnte sie mir rechtzeitig zurückschicken, als ich anfing, CMake zu lernen. Und die Unterfragen sollen zeigen, dass ich tatsächlich versuche zu fragen, was ich wissen sollte, um diese Zweifel nicht zu haben. Die richtige Frage zu stellen ist SCHWER.
leinaD_natipaC
2
Keine Antwort, aber ich lade Sie ein, sich jaws.rootdirectory.de anzuschauen . Während die CMake dokumentation (und die akzeptierte Antwort ...) zeigt Ihnen viele mundgerechte Schnipsel von dem, was Sie könnte tun, schrieb ich auf eine (keineswegs „vollständig“ oder „perfekt“) Basis - Setup, einschließlich der Kommentare im gesamten, dass IMHO zeigt, was CMake (und CTest und CPack und CDash ...) für Sie tun können. Es ist sofort einsatzbereit und kann problemlos an die Anforderungen Ihres Projekts angepasst / erweitert werden.
DevSolar
9
Schade, dass solche Fragen heutzutage geschlossen werden. Ich denke, dass allgemeine Fragen, für die ein Tutorial erforderlich ist, wie Antworten, aber schlecht / dunkel dokumentierte Themen (ein weiteres Beispiel wäre die Torch7 C-API) und Fragen auf Forschungsebene offen bleiben sollten. Diese sind viel interessanter als das typische "Hey, ich habe einen Segfault, wenn ich ein Spielzeugprojekt mache", das die Seite plagt.
Ash

Antworten:

137

Wofür ist CMake?

Laut Wikipedia:

CMake ist [...] Software zur Verwaltung des Erstellungsprozesses von Software mithilfe einer compilerunabhängigen Methode. Es unterstützt Verzeichnishierarchien und Anwendungen, die von mehreren Bibliotheken abhängen. Es wird in Verbindung mit nativen Build-Umgebungen wie make, Apples Xcode und Microsoft Visual Studio verwendet.

Mit CMake müssen Sie keine separaten Einstellungen mehr für Ihre Compiler- / Build-Umgebung vornehmen. Sie haben eine Konfiguration, die in vielen Umgebungen funktioniert .

CMake kann aus denselben Dateien eine Microsoft Visual Studio-Lösung, ein Eclipse-Projekt oder ein Makefile-Labyrinth generieren, ohne etwas daran zu ändern.

Bei einer Reihe von Verzeichnissen mit Code verwaltet CMake alle Abhängigkeiten, Erstellungsaufträge und anderen Aufgaben, die Ihr Projekt ausführen muss, bevor es kompiliert werden kann. Es kompiliert eigentlich nichts. Um CMake verwenden zu können, müssen Sie (unter Verwendung der Konfigurationsdateien CMakeLists.txt) mitteilen, welche ausführbaren Dateien Sie kompilieren müssen, mit welchen Bibliotheken sie verknüpft sind, welche Verzeichnisse sich in Ihrem Projekt befinden und welche darin enthalten sind sowie Details wie Flags oder alles andere, was Sie brauchen (CMake ist ziemlich mächtig).

Wenn dies korrekt eingerichtet ist, verwenden Sie CMake, um alle Dateien zu erstellen, die Ihre "native Build-Umgebung" Ihrer Wahl für ihre Arbeit benötigt. Unter Linux bedeutet dies standardmäßig Makefiles. Sobald Sie CMake ausführen, werden eine Reihe von Dateien für den eigenen Gebrauch sowie einige Makefiles erstellt. Alles, was Sie danach tun müssen, ist, jedes Mal, wenn Sie Ihren Code bearbeitet haben, "make" aus dem Stammordner in die Konsole einzugeben, und bam wird eine kompilierte und verknüpfte ausführbare Datei erstellt.

Wie funktioniert CMake? Was tut es?

Hier ist ein Beispiel für ein Projekt-Setup, das ich durchgehend verwenden werde:

simple/
  CMakeLists.txt
  src/
    tutorial.cxx
    CMakeLists.txt
  lib/
    TestLib.cxx
    TestLib.h
    CMakeLists.txt
  build/

Der Inhalt jeder Datei wird später angezeigt und erläutert.

CMake richtet Ihr Projekt gemäß dem Stammverzeichnis CMakeLists.txt Ihres Projekts ein und zwar in dem Verzeichnis, cmakeaus dem Sie es in der Konsole ausgeführt haben. Wenn Sie dies aus einem Ordner heraus tun, der nicht das Stammverzeichnis Ihres Projekts ist, wird ein so genannter Out-of-Source- Build erstellt. Dies bedeutet, dass Dateien, die während der Kompilierung erstellt wurden (obj-Dateien, lib-Dateien, ausführbare Dateien, wie Sie wissen), in diesem Ordner abgelegt werden , vom eigentlichen Code getrennt gehalten. Es hilft, Unordnung zu reduzieren und wird auch aus anderen Gründen bevorzugt, auf die ich nicht eingehen werde.

Ich weiß nicht, was passiert, wenn Sie cmakeauf einer anderen als der Wurzel ausführen CMakeLists.txt.

In diesem Beispiel muss ich, da alles in den build/Ordner verschoben werden soll, zuerst dorthin navigieren und dann CMake das Verzeichnis übergeben, in dem sich das Stammverzeichnis CMakeLists.txtbefindet.

cd build
cmake ..

Standardmäßig wird hiermit Makefiles wie gesagt eingerichtet. So sollte der Build-Ordner jetzt aussehen:

simple/build/
  CMakeCache.txt
  cmake_install.cmake
  Makefile
  CMakeFiles/
    (...)
  src/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile
  lib/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile

Was sind all diese Dateien? Das einzige, worüber Sie sich Sorgen machen müssen, sind das Makefile und die Projektordner .

Beachten Sie die Ordner src/und lib/. Diese wurden erstellt, weil simple/CMakeLists.txtsie mit dem Befehl auf sie verweisen add_subdirectory(<folder>). Dieser Befehl weist CMake an, in diesem Ordner nach einer anderen CMakeLists.txtDatei zu suchen und dieses Skript auszuführen. Daher muss in jedem auf diese Weise hinzugefügten Unterverzeichnis eine CMakeLists.txtDatei enthalten sein. In diesem Projekt wird simple/src/CMakeLists.txtbeschrieben, wie die eigentliche ausführbare Datei erstellt wird und simple/lib/CMakeLists.txtwie die Bibliothek erstellt wird. Jedes Ziel, das ein CMakeLists.txtbeschreibt, wird standardmäßig in seinem Unterverzeichnis innerhalb des Build-Baums platziert. Also nach einer kurzen

make

In der Konsole von build/werden einige Dateien hinzugefügt:

simple/build/
  (...)
  lib/
    libTestLib.a
    (...)
  src/
    Tutorial
    (...)

Das Projekt wird erstellt und die ausführbare Datei kann ausgeführt werden. Was tun Sie, wenn die ausführbaren Dateien in einem bestimmten Ordner abgelegt werden sollen? Legen Sie die entsprechende CMake-Variable fest oder ändern Sie die Eigenschaften eines bestimmten Ziels . Mehr zu CMake-Variablen später.

Wie sage ich CMake, wie ich mein Projekt erstellen soll?

Hier ist der erläuterte Inhalt jeder Datei im Quellverzeichnis:

simple/CMakeLists.txt::

cmake_minimum_required(VERSION 2.6)

project(Tutorial)

# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)

Die minimal erforderliche Version sollte immer festgelegt werden, entsprechend der Warnung, die CMake auslöst, wenn Sie dies nicht tun. Verwenden Sie eine beliebige Version von CMake.

Der Name Ihres Projekts kann später verwendet werden und weist darauf hin, dass Sie mehr als ein Projekt aus denselben CMake-Dateien verwalten können. Ich werde mich jedoch nicht damit befassen.

Wie bereits erwähnt, wird add_subdirectory()dem Projekt ein Ordner hinzugefügt. Dies bedeutet, dass CMake erwartet, dass es einen Ordner enthält CMakeLists.txt, den es dann ausführt, bevor es fortfährt. Übrigens, wenn Sie eine CMake-Funktion definiert haben, können Sie sie aus anderen CMakeLists.txts in Unterverzeichnissen verwenden, aber Sie müssen sie definieren, bevor Sie sie verwenden, sonst add_subdirectory()wird sie nicht gefunden. CMake ist jedoch schlauer in Bezug auf Bibliotheken, sodass dies wahrscheinlich das einzige Mal ist, dass Sie auf diese Art von Problem stoßen.

simple/lib/CMakeLists.txt::

add_library(TestLib TestLib.cxx)

Um Ihre eigene Bibliothek zu erstellen, geben Sie ihr einen Namen und listen dann alle Dateien auf, aus denen sie erstellt wurde. Einfach. Wenn foo.cxxzum Kompilieren eine andere Datei benötigt wird , schreiben Sie stattdessen add_library(TestLib TestLib.cxx foo.cxx). Dies funktioniert beispielsweise auch für Dateien in anderen Verzeichnissen add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx). Mehr zur Variablen CMAKE_SOURCE_DIR später.

Sie können damit auch angeben, dass Sie eine gemeinsam genutzte Bibliothek möchten. Das Beispiel : add_library(TestLib SHARED TestLib.cxx). Fürchte dich nicht, hier beginnt CMake dein Leben zu erleichtern. Unabhängig davon, ob es freigegeben ist oder nicht, müssen Sie nur noch den Namen verwenden, den Sie hier angegeben haben, um eine auf diese Weise erstellte Bibliothek zu verwenden. Der Name dieser Bibliothek lautet jetzt TestLib, und Sie können von überall im Projekt darauf verweisen . CMake wird es finden.

Gibt es eine bessere Möglichkeit, Abhängigkeiten aufzulisten? Auf jeden Fall ja . Weitere Informationen hierzu finden Sie weiter unten.

simple/lib/TestLib.cxx::

#include <stdio.h>

void test() {
  printf("testing...\n");
}

simple/lib/TestLib.h::

#ifndef TestLib
#define TestLib

void test();

#endif

simple/src/CMakeLists.txt::

# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)

# Link to needed libraries
target_link_libraries(Tutorial TestLib)

# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)

Der Befehl add_executable()funktioniert genauso wie add_library(), außer dass stattdessen eine ausführbare Datei generiert wird. Diese ausführbare Datei kann jetzt als Ziel für Dinge wie referenziert werden target_link_libraries(). Da tutorial.cxx Code aus der TestLib-Bibliothek verwendet, weisen Sie CMake wie gezeigt darauf hin.

Ebenso müssen alle .h-Dateien #, die von Quellen in add_executable()diesem Verzeichnis enthalten sind, die sich nicht im selben Verzeichnis wie die Quelle befinden, irgendwie hinzugefügt werden. Wenn der target_include_directories()Befehl lib/TestLib.hnicht vorhanden ist, wird er beim Kompilieren des Lernprogramms nicht gefunden, sodass der gesamte lib/Ordner zu den Include-Verzeichnissen hinzugefügt wird, in denen nach #includes gesucht werden soll. Möglicherweise wird auch der Befehl include_directories()angezeigt, der sich auf ähnliche Weise verhält, mit der Ausnahme, dass Sie kein Ziel angeben müssen, da es für alle ausführbaren Dateien vollständig global festgelegt wird. Ich werde CMAKE_SOURCE_DIR später noch einmal erklären.

simple/src/tutorial.cxx::

#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
  test();
  fprintf(stdout, "Main\n");
  return 0;
}

Beachten Sie, wie die Datei "TestLib.h" enthalten ist. Sie müssen nicht den vollständigen Pfad angeben: CMake kümmert sich dank all dem um alles hinter den Kulissen target_include_directories().

Technisch gesehen, in einem einfachen Quellbaum wie das Sie ohne die tun können , CMakeLists.txts unter lib/und src/und das Hinzufügen nur so etwas wie add_executable(Tutorial src/tutorial.cxx)zu simple/CMakeLists.txt. Es liegt an Ihnen und den Anforderungen Ihres Projekts.

Was sollte ich noch wissen, um CMake richtig zu verwenden?

(AKA-Themen, die für Ihr Verständnis relevant sind)

Pakete finden und verwenden : Die Antwort auf diese Frage erklärt es besser als ich es jemals könnte.

Deklarieren von Variablen und Funktionen, Verwenden des Kontrollflusses usw . : In diesem Lernprogramm werden die Grundlagen des Angebots von CMake erläutert und eine gute Einführung im Allgemeinen gegeben.

CMake-Variablen : Es gibt viele. Im Folgenden finden Sie einen Crash-Kurs, der Sie auf den richtigen Weg bringt. Das CMake-Wiki ist ein guter Ort, um detailliertere Informationen zu Variablen und angeblich auch zu anderen Dingen zu erhalten.

Möglicherweise möchten Sie einige Variablen bearbeiten, ohne den Build-Baum neu zu erstellen. Verwenden Sie dazu ccmake (es bearbeitet die CMakeCache.txtDatei). Denken cSie daran, eine Konfiguration vorzunehmen, wenn Sie mit den Änderungen fertig sind, und dann gMakefiles mit der aktualisierten Konfiguration zu aktivieren.

Lesen Sie das zuvor erwähnte Tutorial , um mehr über die Verwendung von Variablen zu erfahren, aber kurz gesagt: set(<variable name> value)um eine Variable zu ändern oder zu erstellen. ${<variable name>}um es zu benutzen.

  • CMAKE_SOURCE_DIR: Das Stammverzeichnis der Quelle. Im vorherigen Beispiel ist dies immer gleich/simple
  • CMAKE_BINARY_DIR: Das Stammverzeichnis des Builds. Im vorherigen Beispiel ist dies gleich simple/build/, aber wenn Sie cmake simple/von einem Ordner wie z. B. ausgeführt werden foo/bar/etc/, werden alle Verweise auf CMAKE_BINARY_DIRin diesem Build-Baum /foo/bar/etc.
  • CMAKE_CURRENT_SOURCE_DIR: Das Verzeichnis, in dem sich der Strom CMakeLists.txtbefindet. Dies bedeutet, dass er sich im Laufe der Zeit ändert: Drucken aus simple/CMakeLists.txtErträgen /simpleund Drucken aus simple/src/CMakeLists.txtErträgen /simple/src.
  • CMAKE_CURRENT_BINARY_DIR: Du hast die Idee. Dieser Pfad hängt nicht nur vom Ordner ab, in dem sich der Build befindet, sondern auch vom CMakeLists.txtSpeicherort des aktuellen Skripts.

Warum sind diese wichtig? Quelldateien befinden sich offensichtlich nicht im Build-Baum. Wenn Sie etwas wie target_include_directories(Tutorial PUBLIC ../lib)im vorherigen Beispiel versuchen , ist dieser Pfad relativ zum Build-Baum, dh es ist wie beim Schreiben ${CMAKE_BINARY_DIR}/lib, das nach innen schaut simple/build/lib/. Es sind dort keine .h-Dateien enthalten. höchstens werden Sie finden libTestLib.a. Du willst ${CMAKE_SOURCE_DIR}/libstattdessen.

  • CMAKE_CXX_FLAGS: Flags, die an den Compiler weitergeleitet werden sollen, in diesem Fall an den C ++ - Compiler. Erwähnenswert ist auch, CMAKE_CXX_FLAGS_DEBUGwelche stattdessen verwendet wird, wenn CMAKE_BUILD_TYPEDEBUG eingestellt ist. Es gibt mehr davon; Schauen Sie sich das CMake-Wiki an .
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Sagen Sie CMake, wo alle ausführbaren Dateien beim Erstellen abgelegt werden sollen. Dies ist eine globale Einstellung. Sie können es beispielsweise einstellen bin/und alles ordentlich dort platzieren. EXECUTABLE_OUTPUT_PATHist ähnlich, aber veraltet, falls Sie darauf stoßen.
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Ebenso eine globale Einstellung, um CMake mitzuteilen, wo alle Bibliotheksdateien abgelegt werden sollen.

Zieleigenschaften : Sie können Eigenschaften festlegen, die nur ein Ziel betreffen, sei es eine ausführbare Datei oder eine Bibliothek (oder ein Archiv ... Sie haben die Idee). Hier ist ein gutes Beispiel für die Verwendung (mit set_target_properties().

Gibt es eine einfache Möglichkeit, einem Ziel automatisch Quellen hinzuzufügen? Verwenden Sie GLOB, um alles in einem bestimmten Verzeichnis unter derselben Variablen aufzulisten . Beispielsyntax ist FILE(GLOB <variable name> <directory>/*.cxx).

Können Sie verschiedene Build-Typen angeben? Ja, obwohl ich nicht sicher bin, wie das funktioniert oder welche Einschränkungen dies hat. Es erfordert wahrscheinlich einige if / then'ning, aber CMake bietet einige grundlegende Unterstützung, ohne etwas zu konfigurieren, wie CMAKE_CXX_FLAGS_DEBUGzum Beispiel die Standardeinstellungen für. Sie können Ihren Build-Typ entweder CMakeLists.txtüber die Datei festlegen set(CMAKE_BUILD_TYPE <type>)oder CMake beispielsweise über die Konsole mit den entsprechenden Flags aufrufen cmake -DCMAKE_BUILD_TYPE=Debug.

Gibt es gute Beispiele für Projekte, die CMake verwenden? Wikipedia hat eine Liste von Open-Source-Projekten, die CMake verwenden, wenn Sie sich das ansehen möchten. Online-Tutorials waren in dieser Hinsicht bisher nichts anderes als eine Enttäuschung für mich, aber diese Frage zum Stapelüberlauf hat ein ziemlich cooles und leicht verständliches CMake-Setup. Es ist einen Blick wert.

Verwenden von Variablen aus CMake in Ihrem Code : Hier ist ein schnelles und schmutziges Beispiel (angepasst aus einem anderen Tutorial ):

simple/CMakeLists.txt::

project (Tutorial)

# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)

# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)

simple/TutorialConfig.h.in::

// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

Die von CMake generierte resultierende Datei simple/src/TutorialConfig.h:

// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1

Mit dieser cleveren Verwendung können Sie coole Dinge wie das Ausschalten einer Bibliothek und dergleichen tun. Ich empfehle, sich dieses Tutorial anzusehen , da es einige etwas fortgeschrittenere Dinge gibt, die früher oder später bei größeren Projekten sehr nützlich sein müssen.

Für alles andere steckt Stack Overflow voller spezifischer Fragen und prägnanter Antworten, was für alle außer den Uneingeweihten großartig ist.

leinaD_natipaC
quelle
2
Ich akzeptiere diese Antwort, aber wenn jemand anderes eine bessere, kürzere Antwort schreibt, werde ich das wählen. Ich würde es selbst tun, aber ich habe schon genug unter CMake gelitten.
leinaD_natipaC
6
Danke für diese tolle Arbeit. Hoffe das bekommt mehr Stimmen! :)
LETs
4
Der eher untertriebene Punkt: Mit CMake müssen Sie keine separaten Einstellungen mehr für Ihre Compiler- / Build-Umgebung vornehmen. Sie haben eine Konfiguration, die für (verschiedene Versionen von) Visual Studio, Codeblöcke, einfache Unix-Makefiles und viele andere Umgebungen funktioniert. Aus diesem Grund habe ich mich in erster Linie mit CMake befasst: Es wurde zu einem großen Problem, die Konfigurationen von Autoconf, MSVC 2005 / 32bit und MSVC 2010 / 64bit synchron zu halten. Und als ich den Dreh raus hatte, fügte ich viele weitere Dinge wie Dokumentenerstellung, Testen, Verpacken usw. hinzu, alles plattformübergreifend.
DevSolar
1
@ DevSolar True. Ich mag diese Zeile mehr als Wikipedia, also werde ich sie der Antwort hinzufügen, wenn Sie nichts dagegen haben. Ich werde jedoch nichts darüber sagen, wie man CMake in diesem Sinne richtig einsetzt. Hier geht es mehr darum, nur sagen zu können, was los ist.
leinaD_natipaC
1
@RichieHH Nach dem Beispiel konfigurieren Sie CMake zuerst für die Verwendung von Makefiles, dann verwenden Sie CMake, um die Dateien zu generieren, für die Ihr Projekt erstellt werden muss, und schließlich machen Sie sich keine Gedanken mehr über CMake, da die Aufgabe hier erledigt ist. Sie "sorgen" sich also um das Makefile in dem Sinne, dass Sie es von nun an makein Bash verwenden müssen, um Ihr Projekt zu erstellen.
leinaD_natipaC