En esta página, se describe el framework de la cadena de herramientas, que permite a los autores de reglas separan su lógica de reglas de la selección de herramientas basada en la plataforma. Sí se recomienda leer las reglas y las plataformas páginas antes de continuar. En esta página, se explica por qué se necesitan las cadenas de herramientas, cómo definirlos y usarlos, y la forma en que Bazel selecciona una cadena de herramientas adecuada según de la plataforma.
Motivación
Primero, veamos los problemas que las cadenas de herramientas están diseñadas para resolver. Supongamos que
es escribir reglas para admitir la "barra" de programación centrado en los datos. Tu bar_binary
compilaría archivos *.bar
usando el compilador barc
, una herramienta que
se compila como otro destino en tu lugar de trabajo. Dado que los usuarios que escriben bar_binary
los destinos no deberían especificar una dependencia en el compilador, lo conviertes en
dependencia implícita agregándola a la definición de la regla como un atributo privado.
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],
),
},
)
Ahora //bar_tools:barc_linux
es una dependencia de cada destino bar_binary
, por lo que
se compilará antes que cualquier destino bar_binary
. Se puede acceder a ella mediante el
de implementación de la misma manera que cualquier otro atributo:
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),
)
...
El problema aquí es que la etiqueta del compilador está codificada en bar_binary
, pero
diferentes objetivos pueden necesitar diferentes compiladores según la plataforma en la que
y en qué plataforma se están creando, denominada la
plataforma de destino y plataforma de ejecución, respectivamente. Además, la regla
El autor no necesariamente conoce todas las herramientas y plataformas disponibles, así que
no es posible codificarlos
en la definición de la regla.
Una solución poco ideal sería trasladar la carga a los usuarios haciendo
el atributo _compiler
no privado Entonces, los objetivos individuales podrían
codificadas para compilar
para una plataforma u otra.
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",
)
Puedes mejorar esta solución usando select
para elegir la compiler
según la plataforma:
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",
}),
)
Sin embargo, esto es tedioso y es algo demasiado exigente para todos los usuarios de bar_binary
.
Si este estilo no se usa de manera coherente en todo el espacio de trabajo, genera
que funcionan bien en una sola plataforma, pero fallan cuando se extienden
y multiplataforma. Tampoco aborda el problema de agregar asistencia
para nuevas plataformas y compiladores sin modificar las reglas ni los objetivos existentes.
El framework de la cadena de herramientas resuelve este problema agregando un nivel adicional de o la indirección. En esencia, declaras que tu regla tiene una dependencia abstracta. en algún miembro de una familia de objetivos (un tipo de cadena de herramientas) y Bazel resuelve automáticamente esto en un destino en particular (una cadena de herramientas) según el para las restricciones aplicables de la plataforma. Ni el autor de la regla ni el autor de destino conocer el conjunto completo de plataformas y cadenas de herramientas disponibles.
Reglas de escritura que usan cadenas de herramientas
En el framework de la cadena de herramientas, en lugar de que las reglas dependan directamente de las herramientas, en su lugar, dependen de tipos de cadenas de herramientas. Un tipo de cadena de herramientas es un objetivo simple que representa una clase de herramientas que cumplen la misma función para diferentes y plataformas de Google Cloud. Por ejemplo, puedes declarar un tipo que represente la barra compilador:
# 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")
La definición de la regla de la sección anterior se modifica para que, en lugar de
recibe el compilador como un atributo, declara que consume un
Cadena de herramientas de //bar_tools:toolchain_type
.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
La función de implementación ahora accede a esta dependencia en ctx.toolchains
.
en lugar de ctx.attr
, usando el tipo de la cadena de herramientas como clave.
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"]
devuelve el
Proveedor de ToolchainInfo
de cualquier destino al que Bazel haya resuelto la dependencia de la cadena de herramientas. Los campos del
Los objetos ToolchainInfo
se establecen mediante la regla de la herramienta subyacente. en los próximos
esta regla se define de modo que haya un campo barcinfo
que une
Un objeto BarcInfo
Se describe el procedimiento de Bazel para resolver cadenas de herramientas en los destinos.
a continuación. Solo el destino resuelto de la cadena de herramientas es
Se convirtió en una dependencia del objetivo bar_binary
, no todo el espacio de candidate.
cadenas de herramientas.
Conjuntos de herramientas obligatorios y opcionales
De forma predeterminada, cuando una regla expresa una dependencia de un tipo de cadena de herramientas con una etiqueta desnuda (como se muestra más arriba), el tipo de cadena de herramientas se considera obligatorio. Si Bazel no puede encontrar una cadena de herramientas que coincida (consulta Resolución de la cadena de herramientas a continuación) para una cadena de herramientas obligatoria. este es un error y se detiene el análisis.
En cambio, es posible declarar una dependencia opcional de un tipo de cadena de herramientas, como sigue:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Cuando no se puede resolver un tipo de cadena de herramientas opcional, el análisis continúa.
el resultado de ctx.toolchains["//bar_tools:toolchain_type"]
es None
.
El config_common.toolchain_type
de forma predeterminada es obligatorio.
Se pueden usar los siguientes formularios:
- Tipos de cadenas de herramientas obligatorias:
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)]
- Tipos de cadenas de herramientas opcionales:
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),
],
)
También puedes combinar formas en la misma regla. Sin embargo, si el mismo toolchain type aparece varias veces, tomará la versión más estricta en la que “obligatorio” es más estricto que “opcional”.
Aspectos de escritura que usan cadenas de herramientas
Los aspectos tienen acceso a la misma API de la cadena de herramientas que las reglas: puedes definir los tipos de cadenas de herramientas, accede a cadenas de herramientas a través del contexto y úsalas para generar nuevos con la cadena de herramientas.
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 []
Cómo definir las cadenas de herramientas
Para definir algunas cadenas de herramientas para un tipo de cadena de herramientas determinado, necesitas tres cosas:
Una regla específica del lenguaje que representa el tipo de herramienta o paquete de herramientas. De convención de que el nombre de esta regla tiene el sufijo “_toolchain”.
- Nota: La regla
\_toolchain
no puede crear acciones de compilación. sino que recopila artefactos de otras reglas y los reenvía a la que usa la cadena de herramientas. Esa regla es responsable de crear acciones de compilación.
- Nota: La regla
Varios destinos de este tipo de regla, que representan versiones de la herramienta para diferentes plataformas.
Para cada uno de estos destinos, se establece un objetivo asociado de la ruta
toolchain
para proporcionar metadatos utilizados por el framework de la cadena de herramientas. Este/atoolchain
target también hace referencia altoolchain_type
asociado con esta cadena de herramientas. Esto significa que una regla_toolchain
determinada podría asociarse a cualquiertoolchain_type
, y eso solo en una instancia detoolchain
que use esta regla de_toolchain
que la regla está asociada con untoolchain_type
.
Para nuestro ejemplo en ejecución, esta es una definición de una regla bar_toolchain
. Nuestro
ejemplo tiene solo un compilador, pero otras herramientas, como un vinculador, también pueden
agrupados debajo de él.
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(),
},
)
La regla debe mostrar un proveedor ToolchainInfo
, que se convierte en el objeto que
la regla de consumo se recupera con ctx.toolchains
y la etiqueta del
tipo de cadena de herramientas. ToolchainInfo
, como struct
, puede contener un valor de campo arbitrario.
pares. La especificación de qué campos se agregan exactamente a ToolchainInfo
deben estar claramente documentados en el tipo de cadena de herramientas. En este ejemplo, los valores
Devuelve unidos en un objeto BarcInfo
para volver a usar el esquema definido antes. este
puede ser útil para la validación y la reutilización del código.
Ahora puedes definir objetivos para compiladores barc
específicos.
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",
)
Por último, crearás definiciones de toolchain
para los dos objetivos de bar_toolchain
.
Estas definiciones vinculan los objetivos específicos del lenguaje al tipo de cadena de herramientas y
la información sobre la restricción que le indica a Bazel cuándo se debe
apropiada para una plataforma determinada.
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",
)
El uso de la sintaxis de ruta de acceso relativa anterior sugiere que todas estas definiciones están en el
mismo paquete, pero no hay motivo para el tipo de cadena de herramientas, ya sea
los destinos de la cadena de herramientas y los objetivos de definición de toolchain
no pueden estar todos en
paquetes.
Consulta la go_toolchain
para ver un ejemplo del mundo real.
Conjuntos de herramientas y parámetros de configuración
Una pregunta importante para los autores de reglas es, cuando un objetivo bar_toolchain
es
analizada, qué configuración ve y qué transiciones
debería usarse para las dependencias? En el ejemplo anterior, se usan atributos de cadena, pero
lo que sucedería con una cadena de herramientas más complicada que depende de otros objetivos
en el repositorio de Bazel?
Veamos una versión más compleja de 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(),
},
)
El uso de attr.label
es el mismo que el de una regla estándar,
pero el significado del parámetro cfg
es un poco diferente.
La dependencia de un objetivo (llamado "principal") a una cadena de herramientas a través de una cadena de herramientas.
resolución usa una transición de configuración especial llamada "cadena de herramientas
de transición". La transición de la cadena de herramientas mantiene la configuración igual, excepto
que obliga a la plataforma de ejecución a ser la misma para la cadena de herramientas y para
el elemento superior (de lo contrario, la resolución de la cadena de herramientas podría elegir cualquiera
de ejecución de Compute Engine y no sería necesariamente la misma que la superior). Esta
Permite que cualquier dependencia exec
de la cadena de herramientas también se pueda ejecutar para el
acciones de compilación superiores. Cualquiera de las dependencias de la cadena de herramientas que use cfg =
"target"
(o que no especifique cfg
, ya que "target" es el valor predeterminado) se
para la misma plataforma de destino
que la plataforma superior. Esto permite que las reglas de la cadena de herramientas
aportan bibliotecas (el atributo system_lib
mencionado anteriormente) y herramientas (las
compiler
) a las reglas de compilación que las necesiten. Las bibliotecas del sistema
se vinculan al artefacto final, por lo que deben construirse para el mismo
de procesamiento, mientras que el compilador es una herramienta invocada durante la compilación y debe
pueda ejecutarse en la plataforma de ejecución.
Realiza el registro y la compilación con cadenas de herramientas
En este punto, todos los componentes
están ensamblados, y solo debes hacer
las cadenas de herramientas disponibles
para el procedimiento de resolución de Bazel. Esto lo hace
registrando la cadena de herramientas, ya sea en un archivo MODULE.bazel
con
register_toolchains()
o si pasas los valores de etiquetas de recurso en el comando
con la marca --extra_toolchains
.
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",
# or even
# "//bar_tools/...",
)
Cuando uses patrones de destino para registrar cadenas de herramientas, el orden en el que que se registren cadenas de herramientas individuales se determina según las siguientes reglas:
- Las cadenas de herramientas definidas en el subpaquete de un paquete se registran antes del cadenas de herramientas definidas en el paquete.
- Dentro de un paquete, las cadenas de herramientas se registran en el orden lexicográfico de sus nombres.
Cuando compilas un objetivo que depende de un tipo de cadena de herramientas, se debe considerar La cadena de herramientas se seleccionará según las plataformas de destino y ejecución.
# 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
Bazel verá que //my_pkg:my_bar_binary
se compila con una plataforma que
tiene @platforms//os:linux
y, por lo tanto, resuelve el
Referencia de //bar_tools:toolchain_type
a //bar_tools:barc_linux_toolchain
Con esta acción, se compilará //bar_tools:barc_linux
, pero no
//bar_tools:barc_windows
Resolución de la cadena de herramientas
Procedimiento de resolución de la cadena de herramientas de Bazel para cada destino que usa cadenas de herramientas. determina las dependencias de la cadena de herramientas concreta del objetivo. El procedimiento consta de ingresar un conjunto de tipos de cadenas de herramientas requeridos, la plataforma de destino, la lista de las plataformas de ejecución disponibles y la lista de cadenas de herramientas disponibles. Sus resultados hay una cadena de herramientas seleccionada para cada tipo de cadena de herramientas, así como una ejecución seleccionada para el objetivo actual.
Las plataformas de ejecución y las cadenas de herramientas disponibles se recopilan de la
gráfico de dependencia externa a través de
register_execution_platforms
y
register_toolchains
llamadas en
MODULE.bazel
.
También se pueden especificar plataformas de ejecución y cadenas de herramientas adicionales en el
la línea de comandos mediante
--extra_execution_platforms
y
--extra_toolchains
La plataforma host se incluye automáticamente como una plataforma de ejecución disponible.
Se hace un seguimiento de las plataformas disponibles y las cadenas de herramientas como listas ordenadas de determinismo.
con preferencia a los elementos anteriores de la lista.
El conjunto de cadenas de herramientas disponibles, en orden de prioridad, se crea a partir de
--extra_toolchains
y register_toolchains
:
- Primero se agregan las cadenas de herramientas registradas con
--extra_toolchains
. (En un radio de estas, la última cadena de herramientas tiene la prioridad más alta). - Cadenas de herramientas registradas con
register_toolchains
en la clase externa transitiva gráfico de dependencia, en el siguiente orden: (dentro de estos, el primer mencionado que la cadena de herramientas tiene la prioridad más alta).- Las cadenas de herramientas que registró el módulo raíz (como el
MODULE.bazel
en el raíz del lugar de trabajo); - Las cadenas de herramientas registradas en el archivo
WORKSPACE
del usuario, incluidas las de cualquier macros invocadas desde allí; - Cadenas de herramientas registradas por módulos no raíz (como en, dependencias especificadas por el módulo raíz y sus dependencias, etcétera);
- Cadenas de herramientas registradas en el "sufijo WORKSPACE" solo lo usan los ciertas reglas nativas incluidas en la instalación de Bazel.
- Las cadenas de herramientas que registró el módulo raíz (como el
NOTA: Seudoobjetivos como :all
, :*
y
/...
se ordenan por paquete de Bazel.
que usa un orden lexicográfico.
Los pasos para la resolución son los siguientes.
Una cláusula
target_compatible_with
oexec_compatible_with
coincide con un plataforma si, para cadaconstraint_value
de su lista, la plataforma también tiene queconstraint_value
(ya sea de forma explícita o predeterminada)Si la plataforma tiene objetos
constraint_value
deconstraint_setting
, no a las que hace referencia la cláusula, estos no afectan la coincidencia.Si el destino que se está compilando especifica la Atributo
exec_compatible_with
(o la definición de su regla especifica la argumento deexec_compatible_with
), la lista de plataformas de ejecución disponibles se filtra para quitar cualquiera que no coincida con las restricciones de ejecución.Se filtra la lista de cadenas de herramientas disponibles para quitar cualquier cadena de herramientas. y especificar
target_settings
que no coinciden con la configuración actual.Para cada plataforma de ejecución disponible, se asocia cada tipo de cadena de herramientas con la primera cadena de herramientas disponible, si la hay, que sea compatible con esta ejecución como la plataforma de destino y la plataforma objetivo.
Cualquier plataforma de ejecución que no pudo encontrar una cadena de herramientas obligatoria compatible para uno de sus tipos de cadenas de herramientas. De las plataformas restantes, la el primero se convierte en la plataforma de ejecución del objetivo actual, y su plataforma Las cadenas de herramientas (si las hay) se convierten en dependencias del objetivo.
La plataforma de ejecución elegida se usa para ejecutar todas las acciones a las que genera.
En los casos en que el mismo destino pueda compilarse en varias configuraciones (como (para diferentes CPU) dentro de la misma compilación, se aplica el procedimiento de resolución de forma independiente a cada versión del objetivo.
Si la regla usa grupos de ejecución, cada ejecución El grupo realiza la resolución de la cadena de herramientas por separado, y cada uno tiene su propia ejecución. y cadenas de herramientas.
Depuración de las cadenas de herramientas
Si quieres agregar compatibilidad con la cadena de herramientas a una regla existente, usa el
--toolchain_resolution_debug=regex
. Durante la resolución del conjunto de herramientas, la marca
Proporciona un resultado detallado para tipos de cadenas de herramientas o nombres de objetivos que coinciden con la variable de regex. Tú
Puedes usar .*
para obtener toda la información. Bazel mostrará los nombres de las cadenas de herramientas que contiene.
comprobaciones y omisiones durante el proceso de resolución.
Si quieres ver qué dependencias cquery
son de la cadena de herramientas
usa la marca --transitions
de cquery
:
# 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