Bazel-Anleitung: C++-Projekt erstellen

In dieser Anleitung werden die Grundlagen zum Erstellen von C++-Anwendungen mit Bazel beschrieben. Sie richten Ihren Arbeitsbereich ein und erstellen ein einfaches C++-Projekt, das die wichtigsten Konzepte von Bazel wie Ziele und BUILD-Dateien veranschaulicht. Lesen Sie nach Abschluss dieser Anleitung unter Allgemeine C++-Build-Anwendungsfälle die Informationen zu komplexeren Konzepten wie das Schreiben und Ausführen von C++-Tests.

Geschätzte Dauer: 30 Minuten

Lerninhalte

In dieser Anleitung wird Folgendes beschrieben:

  • Ziel erstellen
  • Abhängigkeiten des Projekts visualisieren
  • Projekt in mehrere Ziele und Pakete aufteilen
  • Zielsichtbarkeit für Pakete steuern
  • Referenzziele über Labels

Hinweis

Zur Vorbereitung auf die Anleitung installieren Sie Bazel, falls noch nicht geschehen. Rufen Sie dann das Beispielprojekt aus dem GitHub-Repository von HBase ab:

git clone https://github.com/bazelbuild/examples

Das Beispielprojekt für diese Anleitung befindet sich im Verzeichnis examples/cpp-tutorial und ist so aufgebaut:

examples
└── cpp-tutorial
    ├──stage1
    │  ├── main
    │  │   ├── BUILD
    │  │   └── hello-world.cc
    │  └── WORKSPACE
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── WORKSPACE
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── WORKSPACE

Wie Sie sehen, gibt es drei Dateigruppen, die jeweils eine Phase in dieser Anleitung darstellen. In der ersten Phase erstellen Sie ein einzelnes Ziel in einem einzelnen Paket. In der zweiten Phase teilen Sie Ihr Projekt in mehrere Ziele auf, speichern es jedoch in einem einzigen Paket. In der dritten und letzten Phase teilen Sie Ihr Projekt in mehrere Pakete auf und erstellen es mit mehreren Zielen.

Mit Bazel erstellen

Arbeitsbereich einrichten

Bevor Sie ein Projekt erstellen können, müssen Sie seinen Arbeitsbereich einrichten. Ein Arbeitsbereich ist ein Verzeichnis, das die Quelldateien Ihres Projekts und die Build-Ausgaben von Bazel enthält. Sie enthält auch Dateien, die Bazel als besonders erkennt:

  • Die Datei WORKSPACE, die das Verzeichnis und seine Inhalte als HBase-Arbeitsbereich identifiziert und sich im Stammverzeichnis der Verzeichnisstruktur des Projekts befindet

  • Eine oder mehrere BUILD-Dateien, die Bazel mitteilen, wie sie verschiedene Teile des Projekts erstellen. Ein Verzeichnis innerhalb des Arbeitsbereichs, das eine BUILD-Datei enthält, ist ein Paket. Später in dieser Anleitung erfahren Sie mehr über Pakete.

Wenn Sie ein Verzeichnis als Bazel-Arbeitsbereich festlegen möchten, erstellen Sie in diesem Verzeichnis eine leere Datei mit dem Namen WORKSPACE.

Wenn Bazel das Projekt erstellt, müssen sich alle Eingaben und Abhängigkeiten im selben Arbeitsbereich befinden. Dateien in verschiedenen Arbeitsbereichen sind unabhängig voneinander, sofern sie nicht verknüpft sind, was aber in dieser Anleitung nicht behandelt werden kann.

Informationen zur BUILD-Datei

Eine BUILD-Datei enthält verschiedene Arten von Anweisungen für Bazel. Der wichtigste Typ ist die Build-Regel, mit der Bazel mitgeteilt wird, wie die gewünschten Ausgaben wie ausführbare Binärdateien oder Bibliotheken erstellt werden sollen. Jede Instanz einer Build-Regel in der Datei BUILD wird als Ziel bezeichnet und verweist auf einen bestimmten Satz von Quelldateien und Abhängigkeiten. Ein Ziel kann auch auf andere Ziele verweisen.

Sehen Sie sich die Datei BUILD im Verzeichnis cpp-tutorial/stage1/main an:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

In unserem Beispiel instanziiert das Ziel hello-world die integrierte Regel cc_binary von Bazel. Die Regel weist Bazel an, eine eigenständige ausführbare Binärdatei aus der Quelldatei hello-world.cc ohne Abhängigkeiten zu erstellen.

Die Attribute im Ziel geben explizit ihre Abhängigkeiten und Optionen an. Das Attribut name ist zwar ein Pflichtfeld, viele sind jedoch optional. Im Ziel hello-world ist z. B. name erforderlich und selbsterklärend. Die Angabe srcs ist optional und gibt die Quelldateien an, aus denen Bazel das Ziel erstellt.

Projekt erstellen

Zum Erstellen des Beispielprojekts wechseln Sie zum Verzeichnis cpp-tutorial/stage1 und führen Folgendes aus:

bazel build //main:hello-world

Im Ziellabel ist der Teil //main: der Speicherort der Datei BUILD im Verhältnis zum Stamm des Arbeitsbereichs und hello-world der Zielname in BUILD -Datei. (Am Ende dieser Anleitung erfahren Sie mehr über Ziellabels.)

Die Ausgabe von Bazel sieht in etwa so aus:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

Glückwunsch, Sie haben soeben Ihr erstes Bazel-Ziel erstellt. Bazel platziert Build-Ausgaben im Verzeichnis bazel-bin im Stammverzeichnis des Arbeitsbereichs. Gehen Sie die Inhalte durch, um eine Vorstellung von der Ausgabestruktur von Bazel zu erhalten.

Testen Sie nun die neu erstellte Binärdatei:

bazel-bin/main/hello-world

Abhängigkeitsdiagramm ansehen

Bei einem erfolgreichen Build sind alle Abhängigkeiten explizit in der Datei BUILD angegeben. Mit diesen Anweisungen erstellt Bazel die Abhängigkeitsgrafik des Projekts, die präzise inkrementelle Builds ermöglicht.

Sie können zur Visualisierung der Abhängigkeiten des Beispielprojekts eine Textdarstellung der Abhängigkeitsgrafik generieren. Führen Sie dazu den folgenden Befehl im Stammverzeichnis des Arbeitsbereichs aus:

bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph

Mit dem obigen Befehl sucht Bazel nach allen Abhängigkeiten für das Ziel //main:hello-world (ausgenommen Host- und implizite Abhängigkeiten) und formatiert die Ausgabe als Grafik.

Fügen Sie den Text dann in GraphViz ein.

Unter Ubuntu können Sie das Diagramm lokal aufrufen, indem Sie GraphViz und den xdot-Punktbetrachter installieren:

sudo apt update && sudo apt install graphviz xdot

Dann können Sie das Diagramm generieren und anzeigen, indem Sie die Textausgabe oben direkt an xdot weiterleiten:

xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph)

Wie Sie sehen, hat die erste Phase des Beispielprojekts ein einzelnes Ziel, das eine einzelne Quelldatei ohne zusätzliche Abhängigkeiten erstellt:

Abhängigkeitsgrafik für hellohello-world“

Abbildung 1. Das Abhängigkeitsdiagramm für hello-world zeigt ein einzelnes Ziel mit einer einzigen Quelldatei an.

Nachdem Sie den Arbeitsbereich eingerichtet, das Projekt erstellt und die Abhängigkeiten untersucht haben, können Sie die Komplexität erhöhen.

Bazel-Build optimieren

Ein einzelnes Ziel ist für kleine Projekte ausreichend, aber Sie können größere Projekte in mehrere Ziele und Pakete aufteilen, um schnelle inkrementelle Builds zu ermöglichen (d. h. nur die Änderungen neu zu erstellen) und um Ihre Builds zu beschleunigen. Mehrere Teile eines Projekts gleichzeitig erstellen

Mehrere Build-Ziele angeben

Sie können den Beispielprojekt-Build in zwei Ziele aufteilen. Sehen Sie sich die Datei BUILD im Verzeichnis cpp-tutorial/stage2/main an:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

Mit dieser BUILD-Datei erstellt Bazel zuerst die hello-greet-Bibliothek mithilfe der integrierten cc_library-Regel von Bazel und dann die hello-world-Binärdatei. aus. Das Attribut deps im Ziel hello-world teilt Bazel mit, dass die Bibliothek hello-greet zum Erstellen der Binärdatei hello-world erforderlich ist.

Erstellen Sie als Nächstes diese neue Version des Projekts. Wechseln Sie in das Verzeichnis cpp-tutorial/stage2 und führen Sie den folgenden Befehl aus:

bazel build //main:hello-world

Die Ausgabe von Bazel sieht in etwa so aus:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

Testen Sie nun die neu erstellte Binärdatei:

bazel-bin/main/hello-world

Wenn Sie jetzt hello-greet.cc ändern und das Projekt neu erstellen, kompiliert Bazel nur diese Datei neu.

Im Abhängigkeitsdiagramm sehen Sie, dass hello-world von den gleichen Eingaben wie zuvor abhängt, aber die Struktur des Builds unterscheidet sich:

Abhängigkeitsgrafik für hellohello-world“

Abbildung 2. Das Abhängigkeitsdiagramm für hello-world zeigt nach der Änderung der Datei Änderungen an der Struktur an.

Sie haben das Projekt mit zwei Zielen erstellt. Das Ziel hello-world erstellt eine Quelldatei und hängt von einem anderen Ziel (//main:hello-greet) ab, das zwei zusätzliche Quelldateien erstellt.

Mehrere Pakete verwenden

Sie können das Projekt in mehrere Pakete aufteilen. Sehen Sie sich den Inhalt des Verzeichnisses cpp-tutorial/stage3 an:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── WORKSPACE

Beachten Sie, dass jetzt zwei Unterverzeichnisse vorhanden sind, von denen jedes eine BUILD-Datei enthält. Daher enthält der Arbeitsbereich in Bazel jetzt die Pakete lib und main.

Sehen Sie sich die Datei lib/BUILD an:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

Und in der Datei main/BUILD:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

Wie Sie sehen, hängt das Ziel hello-world im Paket main vom Ziel hello-time im Paket lib ab (daher das Ziellabel //lib:hello-time): Bazel weiß dies über das Attribut deps. Sehen Sie sich das Abhängigkeitsdiagramm an:

Abhängigkeitsgrafik für hellohello-world“

Abbildung 3. Die Abhängigkeitsgrafik für hello-world zeigt an, wie das Ziel im Hauptpaket vom Ziel im lib-Paket abhängt.

Damit der Build erfolgreich ist, können Sie das Ziel //lib:hello-time in lib/BUILD mithilfe des Attributs visibility explizit für Ziele in main/BUILD sichtbar machen. Der Grund dafür ist, dass Ziele standardmäßig nur für andere Ziele in derselben BUILD-Datei sichtbar sind. (Bazel verwendet die Zielsichtbarkeit, um Probleme zu vermeiden, z. B. Bibliotheken mit Implementierungsdetails, die in öffentliche APIs gelangen.)

Als Nächstes können Sie diese endgültige Version des Projekts erstellen. Wechseln Sie in das Verzeichnis cpp-tutorial/stage3 und führen Sie den folgenden Befehl aus:

bazel build //main:hello-world

Die Ausgabe von Bazel sieht in etwa so aus:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

Testen Sie nun die neu erstellte Binärdatei:

bazel-bin/main/hello-world

Sie haben das Projekt als zwei Pakete mit drei Zielen erstellt und kennen sich mit den Abhängigkeiten untereinander aus.

Mit Labels auf Ziele verweisen

In BUILD-Dateien und in der Befehlszeile verwendet Bazel Labels, um auf Ziele zu verweisen, z. B. //main:hello-world oder //lib:hello-time. Die Syntax lautet:

//path/to/package:target-name

Wenn das Ziel ein Regelziel ist, ist path/to/package der Pfad vom Stammverzeichnis des Arbeitsbereichs (das Verzeichnis, das die Datei WORKSPACE enthält) zum Verzeichnis mit der Datei BUILD. target-name ist der Name, den Sie in der Datei BUILD (das Attribut name) genannt haben. Wenn das Ziel ein Dateiziel ist, ist path/to/package der Pfad zum Stammverzeichnis des Pakets und target-name der Name der Zieldatei, einschließlich ihres vollständigen Pfads relativ. zum Stammverzeichnis des Pakets, also in dem Verzeichnis, das die Datei BUILD des Pakets enthält.

Wenn Sie auf Ziele im Repository-Stammverzeichnis verweisen, ist der Paketpfad leer. Verwenden Sie dafür einfach //:target-name. Beim Verweisen auf Ziele in derselben BUILD-Datei können Sie sogar die Stamm-ID des Arbeitsbereichs // überspringen und einfach :target-name verwenden.

Weitere Informationen

Glückwunsch! Jetzt wissen Sie, wie Sie mit Bazel ein C++-Projekt erstellen. Informieren Sie sich als Nächstes über die häufigsten C++-Build-Anwendungsfälle.

Weitere Informationen finden Sie hier:

Viel Spaß!