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

Makros

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

Auf dieser Seite werden die Grundlagen der Verwendung von Makros sowie typische Anwendungsfälle, Debuggings und Konventionen behandelt.

Ein Makro ist eine Funktion, die aus der Datei BUILD aufgerufen wird, um Regeln zu instanziieren. Makros werden hauptsächlich zum Kapseln und zur Wiederverwendung vorhandener Regeln und Codes verwendet. Am Ende der Ladephase sind keine Makros mehr vorhanden. Bazel sieht dann nur noch den konkreten Satz instanziierter Regeln.

Nutzung

Der typische Anwendungsfall für ein Makro ist, wenn Sie eine Regel wiederverwenden möchten.

Beispiel: Mit „genrule“ in einer BUILD-Datei wird eine Datei mit //:generator generiert, wobei das Argument some_arg im Befehl hartcodiert ist:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

Wenn Sie weitere Dateien mit unterschiedlichen Argumenten generieren möchten, können Sie diesen Code in eine Makrofunktion extrahieren. Lassen Sie uns das Makro file_generator mit den Parametern name und arg aufrufen. Ersetzen Sie die Genrule durch Folgendes:

load("//path:generator.bzl", "file_generator")

file_generator(
    name = "file",
    arg = "some_arg",
)

file_generator(
    name = "file-two",
    arg = "some_arg_two",
)

file_generator(
    name = "file-three",
    arg = "some_arg_three",
)

Hier laden Sie das Symbol file_generator aus einer .bzl-Datei im Paket //path. Wenn Sie Definitionen für Makrofunktionen in einer separaten .bzl-Datei ablegen, bleibt Ihre BUILD-Datei sauber und deklarativ. Die .bzl-Datei kann aus jedem Paket im Arbeitsbereich geladen werden.

Schreiben Sie schließlich in path/generator.bzl die Definition des Makros, um die ursprüngliche Genrule-Definition zu kapseln und zu parametrisieren:

def file_generator(name, arg, visibility=None):
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location //:generator) %s > $@" % arg,
    tools = ["//:generator"],
    visibility = visibility,
  )

Sie können auch Makros verwenden, um Regeln miteinander zu verketten. Dieses Beispiel zeigt verkettete Genrules, bei denen eine Genrule die Ausgaben einer vorherigen Genrule als Eingabe verwendet:

def chained_genrules(name, visibility=None):
  native.genrule(
    name = name + "-one",
    outs = [name + ".one"],
    cmd = "$(location :tool-one) $@",
    tools = [":tool-one"],
    visibility = ["//visibility:private"],
  )

  native.genrule(
    name = name + "-two",
    srcs = [name + ".one"],
    outs = [name + ".two"],
    cmd = "$(location :tool-two) $< $@",
    tools = [":tool-two"],
    visibility = visibility,
  )

Im Beispiel wird der zweiten Genrule nur ein Sichtbarkeitswert zugewiesen. So können Makro-Autoren die Ausgaben von Zwischenregeln ausblenden, die von anderen Zielen im Arbeitsbereich abhängen.

Maximierungs-Makros

Wenn Sie die Funktionsweise eines Makros untersuchen möchten, verwenden Sie den Befehl query mit --output=build, um die erweiterte Form zu sehen:

$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
  name = "file",
  tools = ["//:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location //:generator) some_arg > $@",
)

Native Regeln instanziieren

Native Regeln (Regeln, die keine load()-Anweisung erfordern) können im Modul native instanziiert werden:

def my_macro(name, visibility=None):
  native.cc_library(
    name = name,
    srcs = ["main.cc"],
    visibility = visibility,
  )

Wenn Sie den Paketnamen kennen müssen (z. B. mit welcher BUILD-Datei das Makro aufgerufen wird), verwenden Sie die Funktion native.package_name(). native kann nur in .bzl-Dateien und nicht in WORKSPACE- oder BUILD-Dateien verwendet werden.

Labelauflösung in Makros

Da Makros in der Ladephase ausgewertet werden, werden Labelstrings wie "//foo:bar" in einem Makro relativ zur Datei BUILD, in der das Makro verwendet wird, und nicht relativ zur Datei .bzl, in der sie definiert ist, interpretiert. Dieses Verhalten ist im Allgemeinen nicht für Makros empfehlenswert, die in anderen Repositories verwendet werden sollen, z. B. weil sie Teil eines veröffentlichten Starlark-Regelsatzes sind.

Um das gleiche Verhalten wie bei Starlark-Regeln zu erzielen, fassen Sie die Labelstrings mit dem Konstruktor Label zusammen:

# @my_ruleset//rules:defs.bzl
def my_cc_wrapper(name, deps = [], **kwargs):
  native.cc_library(
    name = name,
    deps = deps + select({
      # Due to the use of Label, this label is resolved within @my_ruleset,
      # regardless of its site of use.
      Label("//config:needs_foo"): [
        # Due to the use of Label, this label will resolve to the correct target
        # even if the canonical name of @dep_of_my_ruleset should be different
        # in the main workspace, such as due to repo mappings.
        Label("@dep_of_my_ruleset//tools:foo"),
      ],
      "//conditions:default": [],
    }),
    **kwargs,
  )

Debugging

  • bazel query --output=build //my/path:all zeigt Ihnen, wie die Datei BUILD nach der Auswertung aussieht. Alle Makros, Globs und Schleifen werden maximiert. Bekannte Einschränkung: select-Ausdrücke werden derzeit nicht in der Ausgabe angezeigt.

  • Sie können die Ausgabe nach generator_function (der Funktion, die die Regeln generiert hat) oder generator_name (dem Namensattribut des Makros) filtern: bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'

  • Mit dem folgenden Trick können Sie herausfinden, wo genau die Regel foo in einer BUILD-Datei generiert wird. Fügen Sie diese Zeile oben in der Datei BUILD ein: cc_library(name = "foo"). Führen Sie Bazel aus. Sie erhalten eine Ausnahme, wenn die Regel foo erstellt wird (aufgrund eines Namenskonflikts). So wird der vollständige Stacktrace angezeigt.

  • Sie können für die Fehlerbehebung auch print verwenden. Die Nachricht wird während der Ladephase als DEBUG-Logzeile angezeigt. Außer in seltenen Fällen müssen Sie entweder print-Aufrufe entfernen oder sie unter einem Parameter debugging festlegen, der standardmäßig False lautet, bevor Sie den Code an das Depot senden.

Fehler

Wenn Sie einen Fehler ausgeben möchten, verwenden Sie die Funktion fail. Erkläre dem Nutzer genau, was schiefgelaufen ist und wie er seine BUILD-Datei korrigieren kann. Es ist nicht möglich, einen Fehler zu erkennen.

def my_macro(name, deps, visibility=None):
  if len(deps) < 2:
    fail("Expected at least two values in deps")
  # ...

Konventionen

  • Alle öffentlichen Funktionen, d. h. Funktionen, die nicht mit einem Unterstrich beginnen, für die Regeln instanziiert werden, müssen ein name-Argument haben. Dieses Argument sollte nicht optional sein (geben Sie keinen Standardwert an).

  • Für öffentliche Funktionen sollte ein Docstring verwendet werden, der den Python-Konventionen entspricht.

  • In BUILD-Dateien muss das Argument name der Makros ein Keyword-Argument (kein Positionsargument) sein.

  • Das Attribut name der von einem Makro generierten Regeln sollte das Namensargument als Präfix enthalten. macro(name = "foo") kann beispielsweise eine cc_library foo und eine Genrule foo_gen generieren.

  • In den meisten Fällen sollten optionale Parameter den Standardwert None haben. None kann direkt an native Regeln übergeben werden. Diese behandeln ihn genauso, als hätten Sie kein Argument übergeben. Sie müssen daher nicht durch 0, False oder [] ersetzt werden. Stattdessen sollte das Makro auf die von ihm erstellten Regeln zurückgreifen, da seine Standardeinstellungen komplex sein oder sich im Laufe der Zeit ändern können. Außerdem sieht ein Parameter, der explizit auf seinen Standardwert festgelegt ist, anders aus als ein Parameter, der nie festgelegt (oder auf None gesetzt) ist, wenn auf ihn über die Abfragesprache oder interne Strukturen des Build-Systems zugegriffen wird.

  • Makros sollten ein optionales Argument visibility haben.