BazelCon 2022 findet vom 16. bis 17. November in New York und online statt.
Jetzt anmelden

Makros verwenden, um benutzerdefinierte Verben zu erstellen

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

Die tägliche Interaktion mit Bazel erfolgt hauptsächlich über einige Befehle: build, test und run. Manchmal fühlt sich diese Beschränkung allerdings an: Sie möchten Pakete in ein Repository übertragen, die Dokumentation für Endnutzer veröffentlichen oder eine Anwendung mit Kubernetes bereitstellen. In Bazel gibt es jedoch keinen publish- oder deploy-Befehl. Wo passen diese Aktionen hin?

Befehl "bazel run"

Da der Schwerpunkt von Bazel auf Hermetischkeit, Reproduzierbarkeit und Inkrementalität liegt, sind die Befehle build und test für die oben genannten Aufgaben nicht hilfreich. Diese Aktionen können in einer Sandbox mit eingeschränktem Netzwerkzugriff ausgeführt werden. Es ist nicht garantiert, dass sie mit jedem bazel build noch einmal ausgeführt werden.

Verlassen Sie sich stattdessen auf bazel run: die Arbeit für Aufgaben, die Nebenwirkungen haben sollen. Bazel-Nutzer sind an Regeln gewöhnt, die ausführbare Dateien erstellen können, und Regelautoren können einen allgemeinen Satz von Mustern verfolgen, um dies auf "benutzerdefinierte Verben" zu erweitern.

In der Wildnis: rules_k8s

Betrachten Sie beispielsweise rules_k8s, die Kubernetes-Regeln für Bazel. Angenommen, Sie haben folgendes Ziel:

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

Die Regel k8s_object erstellt eine standardmäßige Kubernetes-YAML-Datei, wenn bazel build für das Ziel staging verwendet wird. Die zusätzlichen Ziele werden jedoch auch vom Makro k8s_object mit Namen wie staging.apply und :staging.delete erstellt. Diese Build-Skripts führen diese Aktionen aus. Wenn sie mit bazel run staging.apply ausgeführt werden, verhalten sie sich wie unsere eigenen bazel k8s-apply- oder bazel k8s-delete-Befehle.

Ein weiteres Beispiel: ts_api_guardian_test

Dieses Muster ist auch im Angular-Projekt sichtbar. Das ts_api_guardian_test-Makro erzeugt zwei Ziele. Das erste ist ein standardmäßiges nodejs_test-Ziel, das eine generierte Ausgabe mit einer "Golden"-Datei vergleicht (d. h. einer Datei, die die erwartete Ausgabe enthält). Sie kann mit einem normalen bazel test-Aufruf erstellt und ausgeführt werden. In angular-cli können Sie ein solches Ziel mit bazel test //etc/api:angular_devkit_core_api ausführen.

Im Laufe der Zeit muss diese Datei möglicherweise aus berechtigten Gründen aktualisiert werden. Dieses Update manuell zu erstellen, ist mühsam und fehleranfällig. Das Makro stellt also auch ein Ziel nodejs_binary bereit, das die goldene Datei aktualisiert und nicht mit der Datei vergleicht. Tatsächlich kann dasselbe Testskript so geschrieben werden, dass es im Modus verifyBestätigen“ oder acceptAkzeptieren“ ausgeführt wird, je nachdem, wie es aufgerufen wird. Dies folgt dem bereits bekannten Muster: Es gibt keinen nativen bazel test-accept-Befehl, aber der gleiche Effekt kann mit bazel run //etc/api:angular_devkit_core_api.accept erzielt werden.

Dieses Muster kann sehr wirkungsvoll sein und wird sich häufig herausstellen, sobald Sie es erkennen.

Eigene Regeln anpassen

Makros sind das Herzstück dieses Musters. Makros werden wie Regeln verwendet, können aber mehrere Ziele erstellen. In der Regel erstellt er ein Ziel mit dem angegebenen Namen, der die primäre Build-Aktion ausführt. Vielleicht wird dafür eine normale Binärdatei, ein Docker-Image oder ein Archiv des Quellcodes erstellt. In diesem Muster werden zusätzliche Ziele erstellt, um Skripts zu erzeugen, die basierend auf der Ausgabe des primären Ziels Nebeneffekte erzielen, z. B. die Veröffentlichung der resultierenden Binärdatei oder die Aktualisierung der erwarteten Testausgabe.

Zur Veranschaulichung erstellen Sie eine imaginäre Regel, die eine Website mit Sphinx generiert, mit einem Makro, um ein zusätzliches Ziel zu erstellen, mit dem der Nutzer die Veröffentlichung vornehmen kann. Beachten Sie die folgende vorhandene Regel zum Erstellen einer Website mit Sphinx:

_sphinx_site = rule(
     implementation = _sphinx_impl,
     attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)

Betrachten Sie als Nächstes eine Regel wie die folgende, die ein Skript erstellt, das bei der Ausführung die generierten Seiten veröffentlicht:

_sphinx_publisher = rule(
    implementation = _publish_impl,
    attrs = {
        "site": attr.label(),
        "_publisher": attr.label(
            default = "//internal/sphinx:publisher",
            executable = True,
        ),
    },
    executable = True,
)

Definieren Sie schließlich das folgende Makro, um Ziele für beide oben genannten Regeln zu erstellen:

def sphinx_site(name, srcs = [], **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML.
    _sphinx_site(name = name, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

Verwenden Sie in den BUILD-Dateien das Makro so, als würde es nur das primäre Ziel erstellen:

sphinx_site(
    name = "docs",
    srcs = ["index.md", "providers.md"],
)

In diesem Beispiel wird ein docsdocs“-Ziel erstellt, so als wäre das Makro eine standardmäßige, einzelne Bazel-Regel. Wenn die Regel erstellt ist, generiert sie eine Konfiguration und führt Sphinx aus, um eine HTML-Website zu erstellen, die manuell überprüft werden kann. Es wird jedoch auch ein zusätzliches "docs.publish"-Ziel erstellt, das ein Skript für die Veröffentlichung der Website erstellt. Nachdem Sie die Ausgabe des primären Ziels geprüft haben, können Sie sie mit bazel run :docs.publish wie einen imaginären bazel publish-Befehl für die öffentliche Nutzung veröffentlichen.

Es ist nicht sofort offensichtlich, wie die Implementierung der Regel _sphinx_publisher aussehen könnte. Bei solchen Aktionen wird häufig ein Launcher-Shell-Skript geschrieben. Diese Methode umfasst in der Regel die Verwendung von ctx.actions.expand_template, um ein sehr einfaches Shell-Skript zu schreiben, in diesem Fall das Binärprogramm des Publishers mit einem Pfad zur Ausgabe des primären Ziels. aus. So kann die Publisher-Implementierung generisch bleiben, die Regel _sphinx_site kann einfach HTML generieren und dieses kleine Skript ist nur erforderlich, um die beiden zu kombinieren.

In rules_k8s wird tatsächlich .apply ausgeführt: expand_template schreibt ein sehr einfaches Bash-Skript auf Basis von apply.sh.tpl, der kubectl mit der Ausgabe des primären Ziels ausführt. Dieses Skript kann dann mit bazel run :staging.apply erstellt und ausgeführt werden. Dabei wird der Befehl k8s-apply für k8s_object-Ziele effektiv bereitgestellt.