En esta página, se describen los beneficios y el uso básico de los parámetros de configuración de Starlark, API de Bazel para personalizar la forma en que se compila tu proyecto. Incluye cómo definir de compilación y proporciona ejemplos.
Esto permite hacer lo siguiente:
- definir marcas personalizadas para tu proyecto, lo que deja obsoleta la necesidad de
--define
- escritura
Transitions para configurar las dependencias
parámetros de configuración diferentes a los de sus superiores
(como
--compilation_mode=opt
o--cpu=arm
) - Generar mejores valores predeterminados en las reglas (como compilar
//my:android_app
automáticamente) con un SDK especificado)
y mucho más, todo desde archivos .bzl (no se requiere la versión de Bazel). Consulta la
bazelbuild/examples
repositorio de
ejemplos.
Configuración de compilación definida por el usuario
Un parámetro de configuración de compilación es una pieza
configuración
información. Piensa en una configuración como un mapa de clave-valor. Configurando --cpu=ppc
y --copt="-DFoo"
produce una configuración que se ve de la siguiente manera:
{cpu: ppc, copt: "-DFoo"}
Cada entrada es una configuración de compilación.
Las marcas tradicionales, como cpu
y copt
, son parámetros de configuración nativos:
se definen sus claves y sus valores se establecen dentro del código Java nativo de Bazel.
Los usuarios de Bazel solo pueden leerlos y escribirlos a través de la línea de comandos
y otras APIs se mantienen de forma nativa. Cambia las marcas nativas y las APIs
que las exponen, requiere una versión de Bazel. Compilación definida por el usuario
parámetros de configuración se definen en los archivos .bzl
(por lo que no se necesita una versión de Bazel para
registrar cambios). También se pueden configurar con la línea de comandos.
(si están designados como flags
, consulta más información a continuación), pero también se pueden
se establecen mediante transiciones definidas por el usuario.
Cómo definir configuraciones de compilación
El parámetro build_setting
rule()
Los parámetros de configuración de compilación son reglas como cualquier otra regla y se diferencian con el
build_setting
de la función rule()
de Starlark
atributo.
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
El atributo build_setting
toma una función que designa el tipo de
configuración de compilación. El tipo se limita a un conjunto de tipos básicos de Starlark, como
bool
y string
. Consulta el módulo config
documentación para obtener más detalles. Puede ser más complicado escribir
en la función de implementación de la regla. Sigue leyendo para obtener más información.
Las funciones del módulo config
toman un parámetro booleano opcional, flag
,
que está configurado como falso de forma predeterminada. si flag
se establece como verdadero, la configuración de la compilación
lo pueden configurar los usuarios en la línea de comandos, así como los escritores de reglas de forma interna.
mediante valores predeterminados y transiciones.
Los usuarios no deben establecer todos los parámetros de configuración. Por ejemplo, si, como regla,
tienen algún modo de depuración
que quieres activar en las reglas de prueba
no queremos dar a los usuarios la posibilidad de activar indiscriminadamente
dentro de otras reglas que no son de prueba.
Usa ctx.build_setting_value
Como todas las reglas, las reglas de configuración de compilación tienen funciones de implementación.
Se puede acceder al valor básico de Starlark de la configuración de la compilación a través del
ctx.build_setting_value
. Este método solo está disponible para
Objetos ctx
de las reglas de configuración de la compilación Estas implementaciones
pueden reenviar el valor de la configuración de compilación o realizar trabajo adicional en
como verificación de tipos o creación de structs más complejos. Así es como harías
implementar una configuración de compilación de tipo enum
:
# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])
temperatures = ["HOT", "LUKEWARM", "ICED"]
def _impl(ctx):
raw_temperature = ctx.build_setting_value
if raw_temperature not in temperatures:
fail(str(ctx.label) + " build setting allowed to take values {"
+ ", ".join(temperatures) + "} but was set to unallowed value "
+ raw_temperature)
return TemperatureProvider(type = raw_temperature)
temperature = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
Define marcas de cadena de varios conjuntos
La configuración de cadenas tiene un parámetro allow_multiple
adicional que permite a las
que se establezca varias veces en la línea de comandos o en Bazelrcs. Su configuración predeterminada
aún se establece con un atributo de tipo de cadena:
# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
name = "roasts",
build_setting_default = "medium"
)
Cada configuración de la marca se trata como un valor único:
$ bazel build //my/target --//example:roasts=blonde \
--//example:roasts=medium,dark
Lo anterior se analiza como {"//example:roasts": ["blonde", "medium,dark"]}
y
ctx.build_setting_value
muestra la lista ["blonde", "medium,dark"]
.
Cómo crear instancias de la configuración de compilación
Las reglas definidas con el parámetro build_setting
tienen un valor obligatorio implícito
build_setting_default
. Este atributo adquiere el mismo tipo que
declarada por el parámetro build_setting
.
# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])
def _impl(ctx):
return FlavorProvider(type = ctx.build_setting_value)
flavor = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
Configuración predefinida
El Skylib incluye un conjunto de parámetros de configuración predefinidos de los que puedes crear una instancia sin tener para escribir Starlark personalizado.
Por ejemplo, para definir una configuración que acepte un conjunto limitado de valores de cadena:
# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "myflag",
values = ["a", "b", "c"],
build_setting_default = "a",
)
Para obtener una lista completa, consulta Reglas comunes de configuración de compilación
Cómo usar la configuración de compilación
Según la configuración de la compilación
Si un destino desea leer información de configuración, puede dependen directamente de la configuración de compilación mediante una dependencia de atributo regular.
# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
...
drink_rule = rule(
implementation = _rule_impl,
attrs = {
"flavor": attr.label()
}
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
drink_rule(
name = "my_drink",
flavor = ":favorite_flavor",
)
Los idiomas pueden crear un conjunto canónico de configuraciones de compilación con todas las reglas
de ese lenguaje. Si bien el concepto nativo de fragments
ya no
existe como un objeto codificado en el mundo de configuración de Starlark, una forma de
significa usar conjuntos de atributos implícitos comunes. Por ejemplo:
# kotlin/rules.bzl
_KOTLIN_CONFIG = {
"_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
"_mode": attr.label(default = "//kotlin/config:mode-flag"),
...
}
...
kotlin_library = rule(
implementation = _rule_impl,
attrs = dicts.add({
"library-attr": attr.string()
}, _KOTLIN_CONFIG)
)
kotlin_binary = rule(
implementation = _binary_impl,
attrs = dicts.add({
"binary-attr": attr.label()
}, _KOTLIN_CONFIG)
Cómo usar la configuración de compilación en la línea de comandos
De manera similar a la mayoría de las marcas nativas, puedes usar la línea de comandos para establecer configuraciones de compilación.
marcadas como marcas. La compilación
el nombre de la configuración es la ruta de destino completa con la sintaxis name=value
:
$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed
Se admite la sintaxis booleana especial:
$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag
Usa alias de configuración de compilación
Puedes establecer un alias para la ruta de acceso de destino de tu configuración de la compilación con el objetivo de que sea más fácil de leer. en la línea de comandos. Los alias funcionan de manera similar a las marcas nativas de la sintaxis de la opción de doble guion.
Agrega --flag_alias=ALIAS_NAME=TARGET_PATH
para establecer un alias
a tu .bazelrc
. Por ejemplo, para establecer un alias en coffee
, haz lo siguiente:
# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp
Práctica recomendada: Configurar un alias varias veces da como resultado la versión más reciente que tenga prioridad. Usa nombres de alias únicos para evitar resultados de análisis no deseados.
Para usar el alias, escríbelo en lugar de la ruta de acceso de destino de la configuración de la compilación.
Con el ejemplo anterior de coffee
configurado en el .bazelrc
del usuario, ocurre lo siguiente:
$ bazel build //my/target --coffee=ICED
en lugar de
$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED
Práctica recomendada: Si bien es posible establecer alias en la línea de comandos, dejarlos
en una .bazelrc
reduce el desorden en la línea de comandos.
Configuración de compilación con tipo de etiqueta
A diferencia de otros parámetros de configuración de compilación, los ajustes de tipo etiqueta no se pueden definir usando
Parámetro de la regla build_setting
. En su lugar, Bazel tiene dos reglas integradas:
label_flag
y label_setting
. Estas reglas reenvían a los proveedores de la
el destino real en el que se configuró la configuración de compilación. label_flag
y
label_setting
se puede leer o escribir mediante transiciones y se puede establecer label_flag
.
por el usuario, al igual que otras reglas build_setting
. La única diferencia es que
no se pueden definir de forma personalizada.
Con el tiempo, la configuración de tipo de etiqueta reemplazará la funcionalidad de límite tardío.
los valores predeterminados. Los atributos predeterminados
con límite tardío son atributos de tipo etiqueta
los valores finales pueden verse afectados por la configuración. En Starlark, esto reemplazará
el configuration_field
en la API de Cloud.
# example/rules.bzl
MyProvider = provider(fields = ["my_field"])
def _dep_impl(ctx):
return MyProvider(my_field = "yeehaw")
dep_rule = rule(
implementation = _dep_impl
)
def _parent_impl(ctx):
if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
...
parent_rule = rule(
implementation = _parent_impl,
attrs = { "my_field_provider": attr.label() }
)
# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")
dep_rule(name = "dep")
parent_rule(name = "parent", my_field_provider = ":my_field_provider")
label_flag(
name = "my_field_provider",
build_setting_default = ":dep"
)
Configuración de compilación y select()
Los usuarios pueden definir atributos en la configuración de compilación con
select()
Los objetivos de configuración de compilación se pueden pasar al atributo flag_values
de
config_setting
El valor que debe coincidir con la configuración se pasa como un
Luego, String
se analiza según el tipo de configuración de compilación para la coincidencia.
config_setting(
name = "my_config",
flag_values = {
"//example:favorite_flavor": "MANGO"
}
)
Transiciones definidas por el usuario
Una configuración transición asigna la transformación de un destino configurado a otro gráfico de compilación.
Definición
Las transiciones definen los cambios de configuración entre reglas. Por ejemplo, una solicitud como "compilar mi dependencia para una CPU diferente a la de su superior" es manejado por un transición.
De manera formal, una transición es una función de una configuración de entrada a una o más
configuraciones de salida. La mayoría de las transiciones son 1:1, por ejemplo, “anular la entrada
de Terraform con --cpu=ppc
”. También pueden existir transiciones 1:2 o más, pero
con restricciones especiales.
En Starlark, las transiciones se definen como reglas, con una definición
transition()
función
y una función de implementación.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//example:favorite_flavor" : "MINT"}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
La función transition()
toma una función de implementación, un conjunto de
configuración de compilación para leer(inputs
) y un conjunto de configuraciones de compilación para escribir
(outputs
). La función de implementación tiene dos parámetros, settings
y
attr
settings
es un diccionario {String
:Object
} de todas las opciones de configuración declaradas
en el parámetro inputs
a transition()
.
attr
es un diccionario de atributos y valores de la regla a la que
si se adjunta una transición. Si se adjunta como un
transición perimetral saliente, los valores de estas
son la resolución post-select() configurada. Cuando se adjunta como
una transición perimetral entrante, attr
no
incluir cualquier atributo que use un selector para resolver su valor. Si un
La transición perimetral entrante en --foo
lee el atributo bar
y, luego,
selecciona --foo
para establecer el atributo bar
, existe la posibilidad de que el valor
transición perimetral entrante para leer el valor incorrecto de bar
en la transición.
La función de implementación debe mostrar un diccionario (o una lista de
diccionarios, en el caso de
transiciones con múltiples configuraciones de salida)
de nuevos valores de configuración de compilación que se pueden aplicar. Los conjuntos de claves del diccionario que se muestran deben
contienen exactamente el conjunto de configuraciones de compilación que se pasaron a outputs
parámetro de la función de transición. Esto se aplica incluso si se configura
no se haya modificado en el transcurso de la transición, su valor original se debe
se pasen explícitamente en el diccionario devuelto.
Cómo definir transiciones de 1:2 o más
La transición perimetral de salida puede asignar una sola entrada de salida en dos o más configuraciones de salida. Esto es útil para definir que agrupan código multiarquitectónica.
Las transiciones 1:2+ se definen al devolver una lista de diccionarios en el de implementación de transición.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return [
{"//example:favorite_flavor" : "LATTE"},
{"//example:favorite_flavor" : "MOCHA"},
]
coffee_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
También pueden configurar claves personalizadas que la función de implementación de reglas puede usar para leen dependencias individuales:
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
Cómo adjuntar transiciones
Las transiciones se pueden adjuntar en dos lugares: bordes entrantes y bordes salientes. Esto significa que las reglas pueden realizar la transición de su propia configuración (entrante transición perimetral) y la transición de sus dependencias de configuración (salientes transición perimetral).
NOTA: Actualmente, no hay forma de adjuntar transiciones de Starlark a las reglas nativas. Si necesitas hacerlo, comunícate con bazel-discuss@googlegroups.com a fin de obtener ayuda para encontrar soluciones.
Transiciones perimetrales entrantes
Las transiciones perimetrales entrantes se activan cuando se adjunta un objeto transition
(creado por transition()
) al parámetro cfg
de rule()
:
# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
implementation = _impl,
cfg = hot_chocolate_transition,
...
Las transiciones perimetrales entrantes deben ser transiciones 1:1.
Transiciones de borde salientes
Las transiciones de borde salientes se activan cuando se adjunta un objeto transition
(creado por transition()
) en el parámetro cfg
de un atributo:
# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
implementation = _impl,
attrs = { "dep": attr.label(cfg = coffee_transition)}
...
Las transiciones de borde salientes pueden ser 1:1 o 1:2+.
Consulta Cómo acceder a los atributos con transiciones sobre cómo leer estas claves.
Transiciones en opciones nativas
Las transiciones de Starlark también pueden declarar operaciones de lectura y escritura en la compilación nativa opciones de configuración con un prefijo especial en el nombre de la opción.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//command_line_option:cpu": "k8"}
cpu_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
Opciones nativas no compatibles
Bazel no admite la transición en --define
con
"//command_line_option:define"
En su lugar, usa una
parámetro de configuración de compilación. En general, los nuevos usos de
No se recomienda usar --define
para usar la configuración de compilación.
Bazel no admite la transición en --config
. Esto se debe a que --config
es
una “expansión” que se expande a otras marcas.
Cabe destacar que --config
puede incluir marcas que no afectan la configuración de compilación.
como, por ejemplo,
--spawn_strategy
de Google Cloud. Por su diseño, Bazel no puede vincular esas marcas a destinos individuales. Esto significa
no hay una manera coherente
de aplicarlos en las transiciones.
Como solución alternativa, puedes detallar explícitamente las marcas que son parte de
la configuración en tu transición. Esto requiere mantener los --config
's
en dos lugares, lo que es una mancha conocida de la IU.
Transiciones para permitir múltiples configuraciones de compilación
Cuando establezcas parámetros de configuración de compilación permitir varios valores, el valor de la debe establecerse con una lista.
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
# Using a value of just "dark" here will throw an error
return {"//example:roasts" : ["dark"]},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:roasts"]
)
Transiciones no-ops
Si una transición muestra {}
, []
o None
, esto es una abreviatura para mantener todos
configuración en sus valores originales. Esto puede ser más conveniente que explícitamente
configurando cada salida para sí misma.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (attr)
if settings["//example:already_chosen"] is True:
return {}
return {
"//example:favorite_flavor": "dark chocolate",
"//example:include_marshmallows": "yes",
"//example:desired_temperature": "38C",
}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = ["//example:already_chosen"],
outputs = [
"//example:favorite_flavor",
"//example:include_marshmallows",
"//example:desired_temperature",
]
)
Cómo acceder a los atributos con transiciones
Cuando conectas una transición a un borde saliente
(independientemente de si la transición es de 1:1 o de 1:2 o más), ctx.attr
se fuerza a ser una lista.
si todavía no lo está. No se especifica el orden de los elementos en esta lista.
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
return {"//example:favorite_flavor" : "LATTE"},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
def _rule_impl(ctx):
# Note: List access even though "dep" is not declared as list
transitioned_dep = ctx.attr.dep[0]
# Note: Access doesn't change, other_deps was already a list
for other_dep in ctx.attr.other_deps:
# ...
coffee_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = coffee_transition)
"other_deps": attr.label_list(cfg = coffee_transition)
})
Si la transición es 1:2+
y establece claves personalizadas, se puede usar ctx.split_attr
para leer las dependencias individuales para cada clave:
# example/transitions/rules.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
def _rule_impl(ctx):
apple_dep = ctx.split_attr.dep["Apple deps"]
linux_dep = ctx.split_attr.dep["Linux deps"]
# ctx.attr has a list of all deps for all keys. Order is not guaranteed.
all_deps = ctx.attr.dep
multi_arch_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = multi_arch_transition)
})
Ver ejemplo completo aquí.
Integración con plataformas y cadenas de herramientas
Muchas banderas nativas hoy en día, como --cpu
y --crosstool_top
, están relacionadas con
resolución de la cadena de herramientas. En el futuro, las transiciones explícitas a estos tipos de
es probable que las marcas se reemplacen con una transición en el
plataforma de destino.
Consideraciones de memoria y rendimiento
Agregar transiciones (y, por lo tanto, nuevas configuraciones) a tu compilación tiene un Costo: gráficos de compilación más grandes, gráficos de compilación menos comprensibles y más lentos. compilaciones. Vale la pena considerar estos costos con transiciones en tus reglas de compilación. A continuación, se muestra un ejemplo de cómo una transición podría crear un crecimiento exponencial en tu gráfico de compilación.
Compilaciones con comportamiento inadecuado: un caso de éxito
Figura 1: Grafo de escalabilidad que muestra un objetivo de nivel superior y sus dependencias.
Este gráfico muestra un objetivo de nivel superior, //pkg:app
, que depende de dos objetivos, un
//pkg:1_0
y //pkg:1_1
. Ambos objetivos dependen de dos objetivos: //pkg:2_0
y
//pkg:2_1
Ambos objetivos dependen de dos objetivos, //pkg:3_0
y //pkg:3_1
.
Esto continúa hasta la(s) //pkg:n_0
y la(s) //pkg:n_1
, que dependen de un solo
objetivo, //pkg:dep
.
La compilación de //pkg:app
requiere \(2n+2\) objetivos:
//pkg:app
//pkg:dep
//pkg:i_0
y//pkg:i_1
para \(i\) en \([1..n]\)
Imagina que implementas una marca
Se aplican --//foo:owner=<STRING>
y //pkg:i_b
depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"
En otras palabras, //pkg:i_b
agrega b
al valor anterior de --owner
para todos.
sus dependencias.
Esto produce los siguientes destinos configurados:
//pkg:app //foo:owner=""
//pkg:1_0 //foo:owner=""
//pkg:1_1 //foo:owner=""
//pkg:2_0 (via //pkg:1_0) //foo:owner="0"
//pkg:2_0 (via //pkg:1_1) //foo:owner="1"
//pkg:2_1 (via //pkg:1_0) //foo:owner="0"
//pkg:2_1 (via //pkg:1_1) //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0) //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1) //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0) //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1) //foo:owner="11"
...
//pkg:dep
produce \(2^n\) objetivos configurados: config.owner=
“\(b_0b_1...b_n\)” para todos \(b_i\) en \(\{0,1\}\).
Esto hace que el gráfico de compilación sea exponencialmente más grande que el gráfico objetivo, con las consecuencias correspondientes de memoria y rendimiento.
PENDIENTE: Agrega estrategias para medir y mitigar estos problemas.
Lecturas adicionales
Para obtener más detalles sobre cómo modificar configuraciones de compilación, consulta:
- Configuración de compilación de Starlark
- Hoja de ruta de configuración de Bazel
- Conjunto completo de ejemplos de extremo a extremo