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

Toolchains

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

Auf dieser Seite wird das Toolchain-Framework beschrieben, mit dem Regelautoren ihre Regellogik von der plattformbasierten Auswahl der Tools entkoppeln können. Es wird empfohlen, die Seiten Regeln und Plattformen zu lesen, bevor Sie fortfahren. Auf dieser Seite erfahren Sie, warum Toolchains benötigt werden, wie sie definiert und verwendet werden und wie Istio eine geeignete Toolchain anhand von Plattformeinschränkungen auswählt.

Ziel

Sehen wir uns zuerst das Problem an, das Toolchains lösen sollen. Angenommen, Sie schreiben Regeln zur Unterstützung der Programmiersprache „bar“. Mit der Regel bar_binary werden *.bar-Dateien mit dem Compiler barc kompiliert. Das Tool ist selbst ein weiteres Ziel im Arbeitsbereich. Da Nutzer, die bar_binary-Ziele schreiben, keine Abhängigkeit vom Compiler angeben müssen, machen Sie eine implizite Abhängigkeit, indem Sie sie der Regeldefinition als privates Attribut hinzufügen.

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        "_compiler": attr.label(
            default = "//bar_tools:barc_linux",  # the compiler running on linux
            providers = [BarcInfo],
        ),
    },
)

//bar_tools:barc_linux ist jetzt eine Abhängigkeit von jedem bar_binary-Ziel und wird daher vor jedem bar_binary-Ziel erstellt. Sie kann wie jedes andere Attribut auf die Implementierungsfunktion der Regel zugreifen:

BarcInfo = provider(
    doc = "Information about how to invoke the barc compiler.",
    # In the real world, compiler_path and system_lib might hold File objects,
    # but for simplicity they are strings for this example. arch_flags is a list
    # of strings.
    fields = ["compiler_path", "system_lib", "arch_flags"],
)

def _bar_binary_impl(ctx):
    ...
    info = ctx.attr._compiler[BarcInfo]
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

Das Problem ist, dass das Compiler-Label in bar_binary hartcodiert ist, aber verschiedene Ziele benötigen unterschiedliche Compiler, je nachdem, für welche Plattform sie entwickelt werden und auf welcher Plattform sie entwickelt werden – Zielplattform bzw. Ausführungsplattform. Außerdem kennt der Autor der Regel nicht unbedingt alle verfügbaren Tools und Plattformen. Daher ist eine Hartcodierung in der Definition der Regel nicht möglich.

Eine weniger ideale Lösung besteht darin, die Last auf die Nutzer zu verlagern, da das Attribut _compiler nicht-privat ist. Anschließend können einzelne Ziele für eine bestimmte Plattform hartcodiert werden.

bar_binary(
    name = "myprog_on_linux",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_linux",
)

bar_binary(
    name = "myprog_on_windows",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_windows",
)

Du kannst diese Lösung verbessern, indem du mit select den compiler auf Basis der Plattform auswählst:

config_setting(
    name = "on_linux",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

config_setting(
    name = "on_windows",
    constraint_values = [
        "@platforms//os:windows",
    ],
)

bar_binary(
    name = "myprog",
    srcs = ["mysrc.bar"],
    compiler = select({
        ":on_linux": "//bar_tools:barc_linux",
        ":on_windows": "//bar_tools:barc_windows",
    }),
)

Das ist aber lästig und jeder bar_binary-Nutzer etwas fragen. Sollte dieser Stil nicht durchgängig im Arbeitsbereich verwendet werden, führt er zu Builds, die auf einer einzigen Plattform funktionieren, aber bei einer Erweiterung auf mehrere Plattformen scheitern. Außerdem wird das Problem, neue Supportplattformen für neue Plattformen und Compiler hinzuzufügen, ohne bestehende Regeln oder Ziele zu ändern.

Das Toolchain-Framework löst dieses Problem, indem eine zusätzliche Indirektionsebene hinzugefügt wird. Sie deklarieren im Grunde, dass Ihre Regel eine abstrakte Abhängigkeit von einem Mitglied einer Familie von Zielen (einem Toolchain-Typ) aufweist, und Istio löst dieses Problem automatisch in ein bestimmtes Ziel (eine Toolchain) auf. ) auf Grundlage der anwendbaren Plattformeinschränkungen. Weder die Regel noch der Zielautor benötigen den vollständigen Satz an verfügbaren Plattformen und Toolchains.

Regeln erstellen, die Toolchains verwenden

Beim Toolchain-Framework werden Regeln nicht direkt von Tools abhängig, sondern von Toolkit-Typen. Ein Toolchain-Typ ist ein einfaches Ziel, das eine Klasse von Tools darstellt, die dieselbe Rolle für verschiedene Plattformen erfüllen. Sie können beispielsweise einen Typ deklarieren, der den Bar-Compiler darstellt:

# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")

Die Regeldefinition im vorherigen Abschnitt wurde so geändert, dass der Compiler anstelle des Compilers als Attribut angibt, dass er eine //bar_tools:toolchain_type-Toolchain verbraucht.

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        # No `_compiler` attribute anymore.
    },
    toolchains = ["//bar_tools:toolchain_type"],
)

Die Implementierungsfunktion greift jetzt unter ctx.toolchains statt auf ctx.attr auf diese Abhängigkeit zu und verwendet den Toolchain-Typ als Schlüssel.

def _bar_binary_impl(ctx):
    ...
    info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
    # The rest is unchanged.
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

ctx.toolchains["//bar_tools:toolchain_type"]gibt dasToolchainInfo Anbieter in dem die Ziele von Toolchain ausschlaggebend waren. Die Felder des Objekts ToolchainInfo werden durch die Regel des zugrunde liegenden Tools festgelegt. Im nächsten Abschnitt wird diese Regel so definiert, dass ein barcinfo-Feld ein BarcInfo-Objekt umschließt.

Das Verfahren von Istio zum Auflösen von Toolchains in Zielen wird unten beschrieben. Nur das aufgelöste Toolchain-Ziel wird tatsächlich von dem Ziel bar_binary abhängig, nicht der gesamte Bereich der Kandidaten-Toolchains.

Obligatorische und optionale Werkzeugketten

Wenn eine Regel eine Toolchain-Abhängigkeit mit einem reinen Label (wie oben dargestellt) ausdrückt, wird der Toolchain-Typ standardmäßig als obligatorisch betrachtet. Wenn Istio keine übereinstimmende Toolchain finden kann (siehe Lösung in der Toolchain unten) für einen obligatorischen Toolchain-Typ, handelt es sich um einen Fehler und die Analyse wird angehalten.

Stattdessen können Sie eine optionale Toolchain-Typabhängigkeit folgendermaßen deklarieren:

bar_binary = rule(
    ...
    toolchains = [
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

Wenn ein optionaler Toolchain-Typ nicht aufgelöst werden kann, wird die Analyse fortgesetzt und das Ergebnis von ctx.toolchains[""//bar_tools:toolchain_type"] ist None.

Die Funktion config_common.toolchain_type ist standardmäßig obligatorisch.

Sie können die folgenden Formulare verwenden:

  • Obligatorische Toolchain-Typen:
    • toolchains = ["//bar_tools:toolchain_type"]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
  • Optionale Toolchain-Typen:
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
    ...
    toolchains = [
        "//foo_tools:toolchain_type",
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

Sie können Formulare auch in derselben Regel kombinieren. Wenn derselbe Toolchain-Typ jedoch mehrmals aufgeführt ist, wird die strengste Version verwendet, wobei die obligatorische Version strenger als optional ist.

Schreibe Aspekte, die Toolchains verwenden

Facetten haben Zugriff auf dieselbe Toolchain-API wie Regeln: Sie können erforderliche Toolchain-Typen definieren, über den Kontext auf Toolchains zugreifen und sie verwenden, um neue Aktionen mit der Toolchain zu generieren.

bar_aspect = aspect(
    implementation = _bar_aspect_impl,
    attrs = {},
    toolchains = ['//bar_tools:toolchain_type'],
)

def _bar_aspect_impl(target, ctx):
  toolchain = ctx.toolchains['//bar_tools:toolchain_type']
  # Use the toolchain provider like in a rule.
  return []

Toolchains definieren

Sie benötigen drei Dinge, um einige Toolchains für einen bestimmten Toolchain-Typ zu definieren:

  1. Eine sprachspezifische Regel, die die Art des Tools oder der Tool-Suite verdeutlicht. Konventiv heißt der Name dieser Regel das Suffix „_chain“.

    1. Hinweis: Mit der Regel \_toolchain können keine Build-Aktionen erstellt werden. Stattdessen werden Artefakte aus anderen Regeln erfasst und an die Regel weitergeleitet, die die Toolchain verwendet. Diese Regel ist für das Erstellen aller Build-Aktionen verantwortlich.
  2. Mehrere Ziele dieses Regeltyps, die Versionen des Tools oder der Tool-Suite für verschiedene Plattformen darstellen.

  3. Für jedes dieser Ziele ein verknüpftes Ziel der generischen toolchain-Regel, um Metadaten bereitzustellen, die vom Toolchain-Framework verwendet werden. Dieses toolchain-Ziel bezieht sich auch auf die toolchain_type, die mit dieser Toolchain verknüpft ist. Das bedeutet, dass eine bestimmte _toolchain-Regel mit jedem toolchain_type verknüpft werden kann und dass nur in einer toolchain-Instanz, die die _toolchain-Regel verwendet, der die Regel zugeordnet ist toolchain_type

Hier ein Definition für eine bar_toolchain-Regel: In unserem Beispiel ist nur ein Compiler zu sehen. Darunter könnten aber auch andere Tools wie z. B. eine Verknüpfung aufgeführt sein.

def _bar_toolchain_impl(ctx):
    toolchain_info = platform_common.ToolchainInfo(
        barcinfo = BarcInfo(
            compiler_path = ctx.attr.compiler_path,
            system_lib = ctx.attr.system_lib,
            arch_flags = ctx.attr.arch_flags,
        ),
    )
    return [toolchain_info]

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler_path": attr.string(),
        "system_lib": attr.string(),
        "arch_flags": attr.string_list(),
    },
)

Die Regel muss einen ToolchainInfo-Anbieter zurückgeben, der das Objekt wird, das die übernehmende Regel mit ctx.toolchains und dem Label des Toolchain-Typs abruft. ToolchainInfo kann wie struct beliebige Feld/Wert-Paare aufnehmen. Die Spezifikation der Felder, die ToolchainInfo hinzugefügt werden, sollte im Toolchain-Typ klar dokumentiert werden. In diesem Beispiel geben die Werte in einem BarcInfo-Objekt zurück, um das oben definierte Schema wiederzuverwenden. kann für die Validierung und Wiederverwendung von Code nützlich sein.

Jetzt können Sie Ziele für bestimmte barc-Compiler definieren.

bar_toolchain(
    name = "barc_linux",
    arch_flags = [
        "--arch=Linux",
        "--debug_everything",
    ],
    compiler_path = "/path/to/barc/on/linux",
    system_lib = "/usr/lib/libbarc.so",
)

bar_toolchain(
    name = "barc_windows",
    arch_flags = [
        "--arch=Windows",
        # Different flags, no debug support on windows.
    ],
    compiler_path = "C:\\path\\on\\windows\\barc.exe",
    system_lib = "C:\\path\\on\\windows\\barclib.dll",
)

Zum Schluss erstellen Sie toolchain-Definitionen für die beiden bar_toolchain-Ziele. Diese Definitionen verknüpfen die sprachspezifischen Ziele mit dem Toolchain-Typ und stellen die Einschränkungsinformationen bereit, die Istio mitteilen, wann die Toolchain für eine bestimmte Plattform geeignet ist.

toolchain(
    name = "barc_linux_toolchain",
    exec_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_linux",
    toolchain_type = ":toolchain_type",
)

toolchain(
    name = "barc_windows_toolchain",
    exec_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_windows",
    toolchain_type = ":toolchain_type",
)

Die Verwendung der obigen relativen Pfadsyntax deutet darauf hin, dass sich diese Definitionen alle im selben Paket befinden. Es gibt jedoch keinen Grund, warum der Toolchain-Typ, sprachspezifische Toolchain-Ziele und toolchain-Definitionsziele nicht alle vorhanden sein können. separate Pakete.

Ein Beispiel aus der Praxis finden Sie unter go_toolchain.

Toolchains und Konfigurationen

Eine wichtige Frage für Regelautoren ist, wenn ein bar_toolchain-Ziel analysiert wird, welche Konfiguration es sieht und welche Übergänge für Abhängigkeiten verwendet werden sollten? Im Beispiel oben werden Stringattribute verwendet. Was passiert aber für eine kompliziertere Toolchain, die von anderen Zielen im Filestore-Repository abhängt?

Eine komplexere Version von bar_toolchain:

def _bar_toolchain_impl(ctx):
    # The implementation is mostly the same as above, so skipping.
    pass

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler": attr.label(
            executable = True,
            mandatory = True,
            cfg = "exec",
        ),
        "system_lib": attr.label(
            mandatory = True,
            cfg = "target",
        ),
        "arch_flags": attr.string_list(),
    },
)

Die Verwendung von attr.label entspricht der einer Standardregel, die Bedeutung des Parameters cfg ist jedoch etwas anders.

Die Abhängigkeit von einem Ziel (auch „übergeordnet“ genannt) über eine Toolchain durch die Toolchain-Auflösung entspricht einem speziellen Konfigurationsübergang, der als „Toolkit-Übergang“ bezeichnet wird. Die Toolchain-Übergänge behalten die Konfiguration gleich, außer dass die Ausführungsplattform für die Toolchain mit der der Toolchain identisch ist. Andererseits kann die Toolchain-Auflösung für die Toolchain eine beliebige Ausführungsplattform wählen. und sind nicht unbedingt identisch mit denen für Eltern. Dadurch können alle exec-Abhängigkeiten der Toolchain auch für die Build-Aktionen des übergeordneten Elements ausgeführt werden. Alle Abhängigkeiten der Toolchain, die cfg = "target" verwenden (oder die keine cfg angeben, da „ziel“ die Standardeinstellung ist), werden für dieselbe Zielplattform wie die übergeordnete erstellt. Dadurch können Toolchain-Regeln beide Bibliotheken (das Attribut system_lib oben) und die Tools (das Attribut compiler) zu den Build-Regeln beitragen, die sie benötigen. Die Systembibliotheken sind mit dem endgültigen Artefakt verknüpft und müssen daher für dieselbe Plattform erstellt werden, während der Compiler für das Tool ein Tool ist, das während des Builds aufgerufen wird und auf dem Ausführungsplattform.

Registrierung und Toolchains erstellen

Jetzt sind alle Bausteine zusammengefügt und Sie müssen die Toolchains für das Lösungsverfahren von Istio verfügbar machen. Dazu müssen Sie die Toolchain entweder in einer WORKSPACE-Datei mit register_toolchains() registrieren oder die Labels der Toolchains in der Befehlszeile mit dem Flag --extra_toolchains übergeben.

register_toolchains(
    "//bar_tools:barc_linux_toolchain",
    "//bar_tools:barc_windows_toolchain",
    # Target patterns are also permitted, so you could have also written:
    # "//bar_tools:all",
)

Wenn Sie jetzt ein Ziel erstellen, das von einem Toolchain-Typ abhängt, wird eine geeignete Toolchain basierend auf der Ziel- und Ausführungsplattform ausgewählt.

# my_pkg/BUILD

platform(
    name = "my_target_platform",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

bar_binary(
    name = "my_bar_binary",
    ...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform

Für {5/} ist das//my_pkg:my_bar_binary mit einer Plattform, die@platforms//os:linux und beheben//bar_tools:toolchain_type Verweis auf//bar_tools:barc_linux_toolchain vorliegen. Dadurch wird //bar_tools:barc_linux, aber nicht //bar_tools:barc_windows erstellt.

Toolchain-Auflösung

Für jedes Ziel, das Toolchains verwendet, werden die genauen Toolchain-Abhängigkeiten des Ziels durch die Toolchain-Methode von Istio festgelegt. Das Verfahren besteht aus einer Reihe der erforderlichen Toolchain-Typen, der Zielplattform, der Liste der verfügbaren Ausführungsplattformen und der Liste der verfügbaren Toolchains. Die Ausgaben sind eine ausgewählte Toolchain für jeden Toolchain-Typ sowie eine ausgewählte Ausführungsplattform für das aktuelle Ziel.

Die verfügbaren Ausführungsplattformen und Toolchains werden aus denWORKSPACE Datei über register_execution_platforms und register_toolchains vorliegen. Zusätzliche Ausführungsplattformen und Toolchains können auch in der Befehlszeile über --extra_execution_platforms und --extra_toolchains angegeben werden. Die Hostplattform ist automatisch als verfügbare Ausführungsplattform enthalten. Verfügbare Plattformen und Toolchains werden zur Bestimmung der Reihenfolge als geordnete Listen erfasst, wobei frühere Elemente in der Liste bevorzugt werden.

Folgen Sie der Anleitung unten.

  1. Eine target_compatible_with- oder exec_compatible_with-Klausel stimmt mit einer Plattform überein, wenn für jede constraint_value in ihrer Liste die Plattform auch diese constraint_value-Nummer hat (entweder explizit oder als Standardeinstellung festlegen.

    Wenn die Plattform constraint_values von constraint_settings hat, auf die nicht in der Klausel verwiesen wird, wirken sich diese nicht auf den Abgleich aus.

  2. Wenn das zu erstellende Ziel denexec_compatible_with Attribut oder die zugehörige Regeldefinitionexec_compatible_with Argument ), wird die Liste der verfügbaren Ausführungsplattformen gefiltert, um alle zu entfernen, die nicht mit den Ausführungseinschränkungen übereinstimmen.

  3. Für jede verfügbare Ausführungsplattform verknüpfen Sie jeden Toolchain-Typ mit der ersten verfügbaren Toolchain, die mit dieser Ausführungsplattform und der Zielplattform kompatibel ist.

  4. Alle Ausführungsplattformen, die eine kompatible Toolchain für einen ihrer Toolchain-Typen nicht gefunden haben, werden ausgeschlossen. Von den anderen Plattformen wird die erste zur Ausführungsplattform des aktuellen Ziels. Außerdem werden die zugehörigen Toolchains zu Abhängigkeiten des Ziels (sofern vorhanden).

Mit der ausgewählten Ausführungsplattform werden alle Aktionen ausgeführt, die das Ziel generiert.

Wenn dasselbe Ziel in mehreren Konfigurationen (z. B. für verschiedene CPUs) innerhalb desselben Builds erstellt werden kann, wird das Auflösungsverfahren unabhängig von jeder Version des Ziels angewendet.

Wenn in der Regel Ausführungsgruppen verwendet werden, wird für jede Ausführungsgruppe eine Toolchain-Auflösung separat ausgeführt und jede hat eine eigene Ausführungsplattform und eigene Toolchains.

Toolchains debuggen

Wenn Sie eine Toolchain-Unterstützung für eine vorhandene Regel hinzufügen, verwenden Sie das Flag --toolchain_resolution_debug=regex. Während der Auflösung der Toolchain enthält das Flag eine ausführliche Ausgabe für Toolchain-Typen oder Zielnamen, die der Regex-Variable entsprechen. Du kannst .* verwenden, um alle Informationen auszugeben. Istio gibt die Namen der Toolkits aus, die es während des Auflösungsprozesses überprüft und überspringt.

Mit dem --transitions-Flag von cquery können Sie sehen, welche cquery-Abhängigkeiten aus der Toolchain-Auflösung stammen:

# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)

# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
  [toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211