Mit Bazel Programme erstellen

Auf dieser Seite wird beschrieben, wie Sie ein Programm mit Bazel, der Build-Befehlssyntax und der Zielmustersyntax erstellen.

Schnellstart

Wechseln Sie zum Ausführen von Bazel in das Basisverzeichnis workspace oder eines seiner Unterverzeichnisse und geben Sie bazel ein. Weitere Informationen finden Sie unter Build erstellen, wenn Sie einen neuen Arbeitsbereich erstellen müssen.

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

Verfügbare Befehle

  • analyze-profile: Analysiert Build-Profildaten.
  • aquery: Führt eine Abfrage für das Aktionsdiagramm nach der Analyse aus.
  • build: Erstellt die angegebenen Ziele
  • canonicalize-flags: Kanonisieren Sie Bazel-Flags.
  • clean: Entfernt Ausgabedateien und beendet optional den Server.
  • cquery: Führt eine nach der Analyse abhängige Abfrage des Abhängigkeitsdiagramms aus.
  • dump: Löscht den internen Status des Bazel-Serverprozesses.
  • help: Druckt die Hilfe für Befehle oder den Index aus.
  • info: Zeigt Laufzeitinformationen zum Balizel-Server an.
  • fetch: Ruft alle externen Abhängigkeiten eines Ziels ab.
  • mobile-install: Installiert Apps auf Mobilgeräten
  • query: Führt eine Abhängigkeitsgrafikabfrage aus.
  • run: Führt das angegebene Ziel aus.
  • shutdown: Stoppt den Bazel-Server.
  • test: Erstellt die angegebenen Testziele und führt sie aus.
  • version: Druckt Versionsinformationen für Bazel aus.

Hilfe

  • bazel help command: Druckt Hilfe und Optionen für command.
  • bazel helpstartup_options: Optionen für das JVM-Hosting Bazel.
  • bazel helptarget-syntax: erläutert die Syntax zum Angeben von Zielen
  • bazel help info-keys: Zeigt eine Liste der vom Infobefehl verwendeten Schlüssel an.

Das Tool bazel führt viele Funktionen aus, die als Befehle bezeichnet werden. Am häufigsten werden bazel build und bazel test verwendet. Sie können die Onlinehilfe mit bazel help durchsuchen.

Ziel erstellen

Bevor Sie einen Build starten können, benötigen Sie einen Arbeitsbereich. Ein Arbeitsbereich ist eine Verzeichnisstruktur, die alle Quelldateien enthält, die zum Erstellen Ihrer Anwendung erforderlich sind. Mit Bazel können Sie einen Build aus einem vollständig schreibgeschützten Volume ausführen.

Geben Sie zum Erstellen eines Programms mit Bazel bazel build gefolgt von dem Ziel ein, das Sie erstellen möchten.

bazel build //foo

Nachdem Sie den Befehl zum Erstellen von //foo ausgeführt haben, sieht die Ausgabe in etwa so aus:

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

First, BazelLadevorgänge Alle Pakete in der Abhängigkeitsgrafik Ihres Ziels Dazu gehören deklarierte Abhängigkeiten, Dateien, die direkt in der Datei BUILD des Ziels aufgeführt sind, und transitive Abhängigkeiten, also Dateien, die in den BUILD-Dateien aufgeführt sind. Abhängigkeiten des Ziels hinzu. Nachdem alle Abhängigkeiten identifiziert wurden, werden sie von Bazel auf ihre Richtigkeit analysiert und die Build-Aktionen erstellt. Als Letztes werden die Compiler und andere Tools des Builds ausgeführt.

Während der Ausführung des Builds gibt Bazel Fortschrittsmeldungen aus. Zu den Fortschrittsmeldungen gehören der aktuelle Build-Schritt (z. B. Compiler oder Linker) beim Start und die Anzahl über die Gesamtzahl der Build-Aktionen abgeschlossen. Zu Beginn des Builds steigt die Anzahl der Aktionen insgesamt, da Bazel die gesamte Aktionsgrafik erkennt. Die Zahl stabilisiert sich jedoch innerhalb weniger Sekunden.

Am Ende des Builds gibt Bazel die gewünschten Ziele für die Erstellung der Ausgabedateien aus. Skripts, die Builds ausführen, können diese Ausgabe zuverlässig parsen. Weitere Informationen findest du unter --show_result.

Wenn Sie denselben Befehl noch einmal eingeben, wird der Build sehr viel schneller ausgeführt.

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

Dies ist ein Null-Build. Da sich nichts geändert hat, müssen keine Pakete aktualisiert werden und es sind keine Build-Schritte auszuführen. Wenn sich etwas in "foo" oder dessen Abhängigkeiten geändert hat, würde Bazel einige Build-Aktionen noch einmal ausführen oder einen inkrementellen Build abschließen.

Mehrere Ziele erstellen

Bazel bietet eine Reihe von Möglichkeiten, um die zu erstellenden Ziele anzugeben. Diese werden allgemein als Zielmuster bezeichnet. Diese Syntax wird in Befehlen wie build, test oder query verwendet.

Während Labels verwendet werden, um einzelne Ziele anzugeben, z. B. zum Deklarieren von Abhängigkeiten in BUILD-Dateien, geben die Zielmuster von Bazel mehrere Ziele an. Zielmuster sind eine Generalisierung der Labelsyntax für Sätze von Zielen mit Platzhaltern. Im einfachsten Fall ist jedes gültige Label auch ein gültiges Zielmuster, das eine Gruppe von genau einem Ziel identifiziert.

Alle Zielmuster, die mit // beginnen, werden relativ zum aktuellen Arbeitsbereich aufgelöst.

//foo/bar:wiz Nur das Ziel //foo/bar:wiz.
//foo/bar Gleichbedeutend mit //foo/bar:bar.
//foo/bar:all Alle Regelziele im Paket foo/bar.
//foo/... Alle Regelziele in allen Paketen unter dem Verzeichnis foo.
//foo/...:all Alle Regelziele in allen Paketen unter dem Verzeichnis foo.
//foo/...:* Alle Ziele (Regeln und Dateien) in allen Paketen unter dem Verzeichnis foo.
//foo/...:all-targets Alle Ziele (Regeln und Dateien) in allen Paketen unter dem Verzeichnis foo.
//... Alle Ziele in Paketen im Arbeitsbereich. Dies gilt nicht für Ziele aus externen Repositories.
//:all Alle Ziele im Paket der obersten Ebene, wenn sich die Stammdatei im Stammverzeichnis befindet.

Zielmuster, die nicht mit // beginnen, werden relativ zum aktuellen Arbeitsverzeichnis aufgelöst. In diesen Beispielen wird das Arbeitsverzeichnis foo verwendet:

:foo Entspricht //foo:foo.
bar:wiz Gleichbedeutend mit //foo/bar:wiz.
bar/wiz Gleichbedeutend mit:
  • //foo/bar/wiz:wiz, wenn foo/bar/wiz ein Paket ist
  • //foo/bar:wiz, wenn foo/bar ein Paket ist
  • //foo:bar/wiz andernfalls
bar:all Entspricht //foo/bar:all.
:all Entspricht //foo:all.
...:all Entspricht //foo/...:all.
... Entspricht //foo/...:all.
bar/...:all Entspricht //foo/bar/...:all.

Für rekursive Zielmuster werden standardmäßig Verzeichnis-Symlinks befolgt, mit Ausnahme derer, die auf die Ausgabebasis verweisen, z. B. die praktischen Symlinks, die im Stammverzeichnis des Arbeitsbereichs erstellt werden.

Darüber hinaus folgt Bazel bei der Auswertung rekursiver Zielmuster in einem Verzeichnis, das eine Datei mit dem Namen enthält, keine Symlinks: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/... ist ein Platzhalter für Pakete, der alle Pakete rekursiv unter dem Verzeichnis foo angibt (für alle Stamme des Paketpfads). :all ist ein Platzhalter für Ziele, der allen Regeln innerhalb eines Pakets entspricht. Diese beiden können wie in foo/...:all kombiniert werden. Wenn beide Platzhalter verwendet werden, kann dies zu foo/... abgekürzt werden.

Außerdem:* (oder:all-targets ) ist ein Platzhalter, der mitjedes Ziel in den übereinstimmenden Paketen enthalten, einschließlich Dateien, die normalerweise von keiner Regel erstellt werden, z. B._deploy.jar verknüpfte Dateienjava_binary Regeln.

Dies impliziert, dass :* ein Oberteil von :all angibt. Obwohl diese Syntax möglicherweise verwirrend ist, ermöglicht diese Syntax die Verwendung des vertrauten Platzhalters :all für typische Builds, bei denen das Erstellen von Zielen wie _deploy.jar nicht erwünscht ist.

Darüber hinaus ermöglicht Bazel die Verwendung eines Schrägstrichs anstelle des Doppelpunktes, der für die Labelsyntax erforderlich ist. Dies ist oft praktisch, wenn Sie die Bash-Dateierweiterung verwenden. Beispielsweise entspricht foo/bar/wiz //foo/bar:wiz (wenn es ein Paket foo/bar gibt) oder //foo:bar/wiz (wenn ein Paket foo vorhanden ist).

Viele Bazel-Befehle akzeptieren eine Liste von Zielmustern als Argumente und alle berücksichtigen den Präfixnegationsoperator -. Dies kann verwendet werden, um eine Reihe von Zielen von der Menge zu trennen, die in den vorherigen Argumenten angegeben wurde. Beachten Sie, dass die Reihenfolge wichtig ist. Beispiel:

bazel build foo/... bar/...

bedeutet buildAlle Ziele unter foo erstellen und alle Ziele unter bar erstellen“, während

bazel build -- foo/... -foo/bar/...

bedeutet buildAlle Ziele unter foo erstellen, außer unter foo/bar“. Das Argument -- ist erforderlich, um zu verhindern, dass die nachfolgenden Argumente, die mit - beginnen, als zusätzliche Optionen interpretiert werden.

Beachten Sie jedoch, dass durch die Subtraktion von Zielen auf diese Weise nicht garantiert wird, dass sie nicht erstellt werden, da sie möglicherweise Abhängigkeiten von Zielen sind, die nicht subtrahiert wurden. Wenn es beispielsweise ein Ziel //foo:all-apis gibt, das unter anderem von //foo/bar:api abhängig ist, wird Letzteres beim Erstellen des ersten Ziels erstellt.

Ausrichtung mittags = ["manual"] sind nicht in Platzhalterzielmustern enthalten (... .:* .:all, usw.), wenn sie in Befehlen wie bazel build undbazel test ; Sie sollten solche Testziele mit expliziten Zielmustern in der Befehlszeile angeben, wenn Sie Bazel erstellen und testen möchten. Im Gegensatz dazu führt bazel query eine solche Filterung nicht automatisch aus. Dies würde den Zweck von bazel query umgehen.

Externe Abhängigkeiten abrufen

Standardmäßig lädt Bazel während des Build-Vorgangs externe Abhängigkeiten herunter und symbolisiert sie. Dies kann jedoch unerwünscht sein, weil Sie entweder wissen möchten, wann neue externe Abhängigkeiten hinzugefügt werden, oder weil Sie Abhängigkeiten vorab abrufen möchten (z. B. vor einem Flug, an dem Sie offline sind). ). Wenn Sie verhindern möchten, dass während der Erstellung neue Abhängigkeiten hinzugefügt werden, geben Sie das Flag --fetch=false an. Dieses Flag gilt nur für Repository-Regeln, die nicht auf ein Verzeichnis im lokalen Dateisystem verweisen. Änderungen an local_repository-, new_local_repository- und Android SDK- sowie NDK-Repository-Regeln werden beispielsweise immer unabhängig vom Wert --fetch wirksam .

Wenn Sie das Abrufen während von Builds nicht zulassen und Bazel neue externe Abhängigkeiten findet, schlägt der Build fehl.

Sie können Abhängigkeiten manuell abrufen, indem Sie bazel fetch ausführen. Wenn Sie das Abrufen während des Builds verbieten, müssen Sie bazel fetch ausführen:

  • Bevor Sie Ihre erste Anzeige erstellen.
  • Nachdem Sie eine neue externe Abhängigkeit hinzugefügt haben

Nach der Ausführung muss die Datei erst wieder ausgeführt werden, wenn sich die WORKSPACE-Datei geändert hat.

fetch verwendet eine Liste von Zielen, für die Abhängigkeiten abgerufen werden sollen. Dadurch werden beispielsweise Abhängigkeiten abgerufen, die zum Erstellen von //foo:bar und //bar:baz erforderlich sind:

bazel fetch //foo:bar //bar:baz

Führen Sie folgenden Befehl aus, um alle externen Abhängigkeiten für einen Arbeitsbereich abzurufen:

bazel fetch //...

Sie müssen den bazel-Abruf nicht ausführen, wenn sich alle Ihre Tools (von Bibliotheks-JAR-Dateien bis zum JDK selbst) im Stammverzeichnis Ihres Arbeitsbereichs befinden. Wenn Sie jedoch etwas außerhalb des Verzeichnisses des Arbeitsbereichs verwenden, führt Bazel automatisch bazel fetch aus, bevor bazel build ausgeführt wird.

Repository-Cache

Bazel versucht, mehrmals dieselbe Datei abzurufen, auch wenn die Datei in verschiedenen Arbeitsbereichen benötigt wird oder wenn die Definition eines externen Repositorys geändert wurde, aber trotzdem dieselbe Datei heruntergeladen werden muss. Dazu speichert Bazel alle im Repository-Cache heruntergeladenen Dateien, die sich standardmäßig unter ~/.cache/bazel/_bazel_$USER/cache/repos/v1/ befinden. Der Standort kann durch die Option --repository_cache geändert werden. Der Cache wird von allen Arbeitsbereichen und installierten Versionen von Bazel genutzt. Ein Eintrag wird aus dem Cache abgerufen, wenn Bazel sicher ist, dass er eine Kopie der richtigen Datei hat, wenn also in der Downloadanfrage eine SHA256-Summe der Datei und eine Datei mit dieser {101 angegeben ist. }Hash im Cache. Daher ist die Angabe eines Hashs für jede externe Datei nicht nur eine gute Idee im Hinblick auf die Sicherheit. und unnötige Downloads vermeiden.

Bei jedem Cache-Treffer wird die Änderungszeit der Datei im Cache aktualisiert. So kann die letzte Verwendung einer Datei im Cache-Verzeichnis einfach ermittelt werden, z. B. um den Cache manuell zu bereinigen. Der Cache wird nie automatisch bereinigt, da er möglicherweise eine Kopie einer Datei enthält, die vorgelagert ist.

Verzeichnisse für Distributionsdateien

Das Distributionsverzeichnis ist ein weiterer Bazel-Mechanismus, um unnötige Downloads zu vermeiden. Bazel durchsucht Verteilungsverzeichnisse vor dem Repository-Cache. Der Hauptunterschied besteht darin, dass das Distributionsverzeichnis manuell vorbereitet werden muss.

Mit der Option --distdir=/path/to-directory können Sie zusätzliche schreibgeschützte Verzeichnisse angeben, um nach Dateien zu suchen, anstatt sie abzurufen. Eine Datei wird aus einem solchen Verzeichnis erstellt, wenn der Dateiname dem Basisnamen der URL entspricht und außerdem der Hash der Datei dem in der Downloadanfrage angegebenen entspricht. Dies funktioniert nur, wenn der Datei-Hash in der WORKSPACE-Deklaration angegeben ist.

Obwohl die Bedingung für den Dateinamen nicht für die Richtigkeit erforderlich ist, wird die Anzahl der Kandidatendateien auf eine pro angegebenem Verzeichnis reduziert. Auf diese Weise bleibt die Angabe von Verteilerverzeichnisen effizient, auch wenn die Anzahl der Dateien in einem solchen Verzeichnis sehr groß wird.

Bazel in einer Umgebung mit Lücke ausführen

Um die binäre Größe von Bazel möglichst gering zu halten, werden die impliziten Abhängigkeiten von Bazel über das Netzwerk abgerufen, während sie zum ersten Mal ausgeführt werden. Diese impliziten Abhängigkeiten enthalten Toolchains und Regeln, die möglicherweise nicht für alle erforderlich sind. Android-Tools werden beispielsweise nur beim Erstellen von Android-Projekten entpackt und abgerufen.

Diese impliziten Abhängigkeiten können jedoch Probleme verursachen, wenn Sie Bazel in einer Umgebung mit Luftabstand ausführen. Das gilt auch dann, wenn Sie alle Ihre WORKSPACE-Abhängigkeiten bereitgestellt haben. Zur Lösung dieses Problems können Sie ein Verteilungsverzeichnis mit diesen Abhängigkeiten auf einem Computer mit Netzwerkzugriff vorbereiten und diese dann mit einem Offlineansatz in die Umgebung mit Lüftung übertragen.

Verwenden Sie das Flag --distdir, um das Distributionsverzeichnis vorzubereiten. Sie müssen dies für jede neue Bazel-Version tun, da die impliziten Abhängigkeiten für jeden Release unterschiedlich sein können.

Wenn Sie diese Abhängigkeiten außerhalb Ihrer Airflow-Umgebung erstellen möchten, überprüfen Sie zuerst die Bazel-Quellstruktur in der richtigen Version:

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

Erstellen Sie dann das Tarball-Paket, das die impliziten Laufzeitabhängigkeiten für diese spezifische Bazel-Version enthält:

bazel build @additional_distfiles//:archives.tar

Exportieren Sie dieses Tarball-Verzeichnis in ein Verzeichnis, das in Ihre mit Luft übertragene Umgebung kopiert werden kann. Beachten Sie das Flag --strip-components, da --distdir mit der Verschachtelungsebene der Verzeichnisverzeichnisse ziemlich kompliziert sein kann:

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

Wenn Sie Bazel in Ihrer Umgebung mit Lücke verwenden, übergeben Sie das Flag --distdir, das auf das Verzeichnis verweist. Sie können es der Einfachheit halber als .bazelrc-Eintrag hinzufügen:

build --distdir=path/to/directory

Build-Konfigurationen und Cross-Kompilierung

Alle Eingaben, die das Verhalten und das Ergebnis eines bestimmten Builds angeben, können in zwei unterschiedliche Kategorien unterteilt werden. Der erste Typ sind die intrinsischen Informationen, die in den BUILD-Dateien Ihres Projekts gespeichert werden: die Build-Regel, die Werte ihrer Attribute und der vollständige Satz ihrer transitiven Abhängigkeiten. Die zweite Art sind die externen oder Umgebungsdaten, die vom Nutzer oder vom Build-Tool bereitgestellt werden: die Auswahl der Zielarchitektur, Kompilierungs- und Verknüpfungsoptionen sowie andere Toolchain-Konfigurationsoptionen. Eine vollständige Gruppe von Umgebungsdaten wird als Konfiguration bezeichnet.

In einem Build kann es mehrere Konfigurationen geben. Stellen Sie sich eine Cross-Kompilierung vor, bei der Sie eine ausführbare //foo:bin-Datei für eine 64-Bit-Architektur erstellen, aber Ihre Workstation eine 32-Bit-Maschine ist. Offensichtlich erfordert der Build das Erstellen von //foo:bin mithilfe einer Toolchain, die 64-Bit-Ausführungsprogramme erstellen kann. Das Build-System muss jedoch auch verschiedene Tools erstellen, die während des Builds selbst verwendet werden, z. B. Tools, werden aus der Quelle zusammengestellt und später beispielsweise in einer Genrule verwendet. Diese müssen zur Ausführung auf Ihrer Workstation erstellt werden. Daher können wir zwei Konfigurationen identifizieren: die Hostkonfiguration, die zum Erstellen von Tools verwendet wird, die während des Builds ausgeführt werden, und die Zielkonfiguration. (oder Anfragekonfiguration, aber wir sagen "Zielkonfiguration" häufiger, obwohl dieses Wort bereits viele Bedeutungen hat.) Es wird verwendet, um die Binärdatei zu erstellen, die Sie letztendlich angefordert haben.

In der Regel gibt es viele Bibliotheken, die sowohl das angeforderte Build-Ziel (//foo:bin) als auch mindestens eines der Hosttools erfordern, zum Beispiel einige Basisbibliotheken. Solche Bibliotheken müssen zweimal erstellt werden, einmal für die Hostkonfiguration und einmal für die Zielkonfiguration. Bazel sorgt dafür, dass beide Varianten erstellt und die abgeleiteten Dateien getrennt aufbewahrt werden, um Störungen zu vermeiden. Solche Ziele können in der Regel gleichzeitig erstellt werden, da sie unabhängig voneinander sind. Wenn Sie Fortschrittsmeldungen erhalten, die darauf hinweisen, dass ein bestimmtes Ziel zweimal erstellt wird, ist dies höchstwahrscheinlich die Erklärung.

Bazel verwendet eine der beiden folgenden Möglichkeiten, um die Hostkonfiguration--distinct_host_configuration. Diese boolesche Option ist etwas unauffällig und die Einstellung kann die Geschwindigkeit Ihrer Builds verbessern (oder verschlechtern).

--distinct_host_configuration=false

Wenn diese Option "falsch" lautet, sind die Host- und Anfragekonfigurationen identisch: Alle während des Build erforderlichen Tools werden genauso erstellt wie Zielprogramme. Diese Einstellung bedeutet, dass keine Bibliotheken zweimal während eines einzelnen Builds erstellt werden müssen.

Dies bedeutet jedoch auch, dass sich jede Änderung an Ihrer Anfragekonfiguration auch auf Ihre Hostkonfiguration auswirkt. Dadurch werden alle Tools neu erstellt und alles, was von der Toolausgabe abhängt, wird ebenfalls neu erstellt. So kann beispielsweise eine einfache Verknüpfungsoption zwischen Builds dazu führen, dass alle Tools neu verknüpft werden, dann werden alle Aktionen, die sie verwenden, noch einmal ausgeführt usw., was zu einer sehr großen neu erstellen.

--distinct_host_configuration=true (Standardeinstellung)

Wenn diese Option wahr ist, wird eine völlig andere Hostkonfiguration verwendet, statt für den Host und die Anfrage dieselbe Konfiguration zu verwenden. Die Hostkonfiguration wird folgendermaßen aus der Zielkonfiguration abgeleitet:

  • Sofern --host_crosstool_top nicht angegeben ist, verwenden Sie dieselbe Version von Crosstool (--crosstool_top), die in der Anfragekonfiguration angegeben ist.
  • Verwende den Wert --host_cpu für --cpu (Standardeinstellung: k8).
  • Verwenden Sie die Werte dieser Optionen wie in der Anfragekonfiguration angegeben: --compiler und --use_ijars. Wenn --host_crosstool_top verwendet wird, wird der Wert von --host_cpu verwendet, um: Suchen Sie im Crosstool nach einem default_toolchain und ignorieren Sie --compiler für die Hostkonfiguration.
  • Verwenden Sie den Wert --host_javabase für --javabase
  • Verwenden Sie den Wert --host_java_toolchain für --java_toolchain
  • Optimierte Builds für C++-Code verwenden (-c opt).
  • Keine Debugging-Informationen generieren (--copt=-g0).
  • Debugging-Informationen aus ausführbaren Dateien und freigegebenen Bibliotheken entfernen (--strip=always).
  • Platzieren Sie alle abgeleiteten Dateien an einem speziellen Speicherort, der von den möglichen Anfragekonfigurationen abweicht.
  • Stempel von Binärdateien mit Build-Daten unterdrücken (siehe --embed_*-Optionen).
  • Bei allen anderen Werten wird die Standardeinstellung beibehalten.

Es gibt viele Gründe, warum es besser sein könnte, eine unterschiedliche Hostkonfiguration aus der Anfragekonfiguration auszuwählen. Für einige kann man sich nicht vorstellen, hiervon sollten jedoch zwei angesprochen werden.

Erstens reduzieren Sie mit entfernten, optimierten Binärdateien den Zeitaufwand für das Verknüpfen und Ausführen der Tools, den von den Tools belegten Speicherplatz und die Netzwerk-E/A-Zeit in verteilten Builds.

Zweitens vermeiden Sie durch die Entkopplung der Host- und Anfragekonfigurationen in allen Builds sehr teure Neuerstellungen, die sich aus geringfügigen Änderungen an der Anfragekonfiguration ergeben würden (z. B. durch das Ändern einer Verknüpfungsoption), wie zuvor beschrieben. aus.

Bei bestimmten Builds kann diese Option ein Hindernis sein. Insbesondere Builds, bei denen Konfigurationsänderungen selten sind (insbesondere bestimmte Java-Builds), und Builds, bei denen der Umfang des Codes, der sowohl in Host- als auch in Zielkonfigurationen erstellt werden muss, sehr groß ist, lohnt sich möglicherweise nicht aus.

Inkrementelle Neuerstellungen korrigieren

Eines der Hauptziele des Bazel-Projekts ist es, die korrekte inkrementelle Neuerstellung sicherzustellen. Bei früheren Build-Tools, insbesondere solchen, die auf Make basieren, wurden bei der Implementierung inkrementeller Builds mehrere nicht erwartete Annahmen getroffen.

Erstens erhöht sich der Zeitstempel von Dateien kontinuierlich. Dies ist zwar ein typischer Fall, aber es ist sehr einfach, dieser Annahme widerzugehen. durch die Synchronisierung mit einer früheren Überarbeitung einer Datei verringert sich die Zeit für die Änderung der Datei. Make-basierte Systeme werden nicht neu erstellt.

Generell erkennt Make keine Änderungen an Dateien, aber keine Änderungen an Befehlen. Wenn Sie die Optionen ändern, die in einem bestimmten Build-Schritt an den Compiler übergeben wurden, wird der Compiler von Make nicht noch einmal ausgeführt und die ungültigen Ausgaben des vorherigen Builds müssen mit make clean manuell verworfen werden.

Außerdem ist Make nicht robust gegen das fehlgeschlagene Beenden eines seiner Unterprozesse, nachdem dieser Teilprozess in die Ausgabedatei geschrieben hat. Während die aktuelle Ausführung von Make fehlschlägt, geht der nachfolgende Aufruf von Make blind davon aus, dass die gekürzte Ausgabedatei gültig ist (weil sie neuer ist als ihre Eingaben) und nicht neu erstellt wird. Entsprechend kann eine ähnliche Situation auftreten, wenn der Make-Prozess beendet wird.

Diese und andere Annahmen werden von Bazel vermieden. Unterhält eine Datenbank mit allen bisher abgeschlossenen Arbeiten und lässt einen Build-Schritt nur aus, wenn festgestellt wird, dass die Eingabedateien (und ihre Zeitstempel) mit diesem Build-Schritt übereinstimmen und der Kompilierungsbefehl für Dieser Build-Schritt stimmt genau mit einer in der Datenbank überein und der Satz von Ausgabedateien (und deren Zeitstempel) für den Datenbankeintrag stimmt genau mit den Zeitstempeln der Dateien auf dem Laufwerk überein. Jede Änderung an den Eingabe- oder Ausgabedateien oder am Befehl selbst führt zu einer erneuten Ausführung des Build-Schritts.

Nutzer korrekter inkrementeller Builds haben den Vorteil, dass sie aufgrund von Verwirrung weniger Zeit verschwenden. (Außerdem wird weniger Zeit mit der Wartezeit für Neuerstellungen verbracht, die durch die Verwendung von make clean verursacht werden, unabhängig davon, ob dies nötig oder notwendig ist.)

Build-Konsistenz und inkrementelle Builds

In diesem Fall definieren wir den Status eines Builds als konsistent, wenn alle erwarteten Ausgabedateien vorhanden sind und ihr Inhalt gemäß den Schritten oder Regeln, die zu deren Erstellung erforderlich sind, korrekt ist. Wenn Sie eine Quelldatei bearbeiten, gilt der Status des Builds als inkonsistent und bleibt so lange uneinheitlich, bis Sie das Build-Tool erfolgreich ausgeführt haben. Diese Situation wird als instabil dargestellt, da sie nur vorübergehend ist und durch die Ausführung des Build-Tools wieder wiederhergestellt wird.

Es gibt noch eine weitere Art von Inkonsistenz, die störend ist: stabile Inkonsistenz. Wenn der Build einen stabilen inkonsistenten Zustand erreicht, wird die Konsistenz durch wiederholten erfolgreichen Aufruf des Build-Tools nicht wiederhergestellt: Der Build ist hängen geblieben und die Ausgabedaten bleiben falsch. Stabile inkonsistente Zustände sind der Hauptgrund, aus dem Nutzer von Make (und anderen Build-Tools) den Typ make clean verwenden. Die Erkennung, dass das Build-Tool auf diese Weise nicht erfolgreich war (und dann wiederhergestellt wird), kann sehr zeitaufwendig und sehr frustrierend sein.

Prinzipiell ist die einfachste Methode zur Erstellung eines konsistenten Builds darin, alle vorherigen Build-Ausgaben zu verwerfen und noch einmal von vorn zu beginnen: jeden Build als sauber zu erstellen. Dieser Ansatz ist offensichtlich zu zeitaufwendig, um praktisch zu sein (außer möglicherweise für Release Engineers). Daher muss das Build-Tool inkrementelle Builds ausführen können, ohne die Konsistenz zu beeinträchtigen.

Eine korrekte Analyse der inkrementellen Abhängigkeiten ist schwierig und wie oben beschrieben arbeiten viele andere Build-Tools schlecht, um stabile inkonsistente Zustände während inkrementeller Builds zu vermeiden. Im Gegensatz dazu bietet Bazel die folgende Garantie: Nach einem erfolgreichen Aufruf des Build-Tools, in dem Sie keine Änderungen vorgenommen haben, bleibt der Build in einem konsistenten Zustand. Wenn Sie die Quelldateien während eines Builds bearbeiten, übernimmt Bazel keine Garantie für die Konsistenz des Ergebnisses des aktuellen Builds. Es garantiert jedoch, dass die Ergebnisse des nächsten Builds die Konsistenz wiederherstellen.

Wie bei allen Garantien gibt es auch hier das Kleingedruckte: Es gibt bekannte Möglichkeiten, in einen stabilen inkonsistenten Zustand mit Bazel zu wechseln. Wir können nicht garantieren, dass solche Probleme durch absichtliche Versuche zur Erkennung von Programmfehlern in der inkrementellen Abhängigkeitsanalyse untersucht werden. Wir werden jedoch nach Möglichkeit versuchen, alle stabilen inkonsistenten Zustände zu beheben, die aus dem normalen oder dem " der Verwendung des Build-Tools angemessen.

Wenn Sie einen stabilen inkonsistenten Status mit Bazel feststellen, melden Sie einen Fehler.

Ausführung in einer Sandbox

Bazel verwendet Sandboxes, um sicherzustellen, dass Aktionen hermetisch und korrekt ausgeführt werden. Bazel führt Jobs (Sandbox: Aktionen) in Sandboxes aus, die nur die minimalen Dateien enthalten, die das Tool für seine Arbeit benötigt. Derzeit funktioniert das Ausführen in einer Sandbox unter Linux 3.12 oder höher mit aktivierter Option CONFIG_USER_NS sowie unter macOS 10.11 oder höher.

Bazel gibt eine Warnung aus, wenn Ihr System keine Sandbox unterstützt. Dadurch werden Sie darauf hingewiesen, dass Builds nicht garantiert, dass sie hermetisch sind, und können das Hostsystem auf unbekannte Weise beeinträchtigen. Wenn Sie diese Warnung deaktivieren möchten, können Sie das Flag --ignore_unsupported_sandboxing an Bazel übergeben.

Auf einigen Plattformen wie Google Kubernetes Engine-Clusterknoten oder Debian sind Nutzer-Namespaces aufgrund von Sicherheitsbedenken standardmäßig deaktiviert. Das können Sie in der Datei /proc/sys/kernel/unprivileged_userns_clone prüfen. Wenn sie vorhanden ist und eine Null enthält, können Nutzer-Namespaces mit sudo sysctl kernel.unprivileged_userns_clone=1 aktiviert werden.

In einigen Fällen kann die Bazel-Sandbox aufgrund der Systemeinrichtung keine Regeln ausführen. Das Symptom ist im Allgemeinen ein Fehler, der eine Meldung wie namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory ausgibt. Versuchen Sie in diesem Fall, die Sandbox für Genrules mit --strategy=Genrule=standalone und für andere Regeln mit --spawn_strategy=standalone zu deaktivieren. Melden Sie einen Fehler in unserer Problemverfolgung und geben Sie an, welche Linux-Distribution Sie verwenden, damit wir dies prüfen und eine Lösung in einer nachfolgenden Version korrigieren können.

Phasen eines Builds

Ein Build erfolgt in Bazel in drei verschiedenen Phasen. Wenn Sie als Nutzer den Unterschied zwischen ihnen verstehen, erhalten Sie einen Einblick in die Optionen, die einen Build steuern (siehe unten).

Ladephase

Beim ersten Laden werden alle erforderlichen BUILD-Dateien für die ursprünglichen Ziele und ihre transitive Schließung von Abhängigkeiten geladen, geparst, ausgewertet und im Cache gespeichert.

Beim ersten Build nach dem Start eines Bazel-Servers dauert die Ladephase in der Regel viele Sekunden, da viele BUILD-Dateien aus dem Dateisystem geladen werden. In nachfolgenden Builds erfolgt das Laden sehr schnell, insbesondere wenn sich keine BUILD-Dateien geändert haben.

Zu den in dieser Phase gemeldeten Fehlern gehören das Paket "nicht gefunden", das Ziel nicht gefunden, lexikalische und grammatikalische Fehler in einer BUILD-Datei und Bewertungsfehler.

Analysephase

Die zweite Phase, Analyse, umfasst die semantische Analyse und Validierung jeder Build-Regel, die Konstruktion einer Build-Abhängigkeitsgrafik und die Bestimmung, in welchem Umfang die Build-Regel bearbeitet wird. jeden Schritt des Builds

Wie beim Laden dauert die Analyse einige Sekunden. Allerdings speichert Bazel die Abhängigkeitsgrafik von einem Build zum nächsten und analysiert nur den vorhandenen Inhalt. Dadurch können inkrementelle Builds extrem schnell werden, falls sich die Pakete seit dem vorherigen Build nicht geändert haben.

Zu den in dieser Phase gemeldeten Fehlern gehören unangemessene Abhängigkeiten, ungültige Eingaben für eine Regel und alle regelspezifischen Fehlermeldungen.

Die Lade- und Analysephasen sind schnell, da Bazel unnötige E/A-Vorgänge in dieser Phase vermeidet. In diesem Fall werden nur BUILD-Dateien gelesen, um die auszuführende Arbeit zu bestimmen. Dies ist absichtlich so konzipiert und stellt eine gute Grundlage für Analysetools wie den query-Befehl von Bazel dar, der oberhalb der Ladephase implementiert wird.

Ausführungsphase

Die dritte und letzte Phase des Builds ist die Ausführung. Mit dieser Phase wird sichergestellt, dass die Ausgaben jedes Schritts im Build mit ihren Eingaben, der wiederholten Kompilierung/Verlinkung usw. übereinstimmen. wie gewünscht. In diesem Schritt benötigt der Build die meiste Zeit, von einigen Sekunden bis zu einer Stunde für einen großen Build. Zu den in dieser Phase gemeldeten Fehlern gehören: fehlende Quelldateien, Fehler in einem von einer Build-Aktion ausgeführten Tool oder Fehler eines Tools zum Erstellen des erwarteten Satzes von Ausgaben.