En esta página, se describen los conceptos básicos del uso de macros y se incluyen casos prácticos típicos, depuración y convenciones.
Una macro es una función a la que se llama desde el archivo BUILD
que puede crear instancias de reglas.
Las macros se usan principalmente para encapsular y reutilizar el código de reglas existentes y otras macros. Al final de la
fase de carga, las macros ya no existen,
y Bazel solo ve el conjunto concreto de reglas con instancias.
Uso
El caso de uso típico para una macro es cuando quiere reutilizar una regla.
Por ejemplo, genrule en un archivo BUILD
genera un archivo mediante //:generator
con un argumento some_arg
codificado en el comando:
genrule(
name = "file",
outs = ["file.txt"],
cmd = "$(location //:generator) some_arg > $@",
tools = ["//:generator"],
)
Si deseas generar más archivos con diferentes argumentos, te recomendamos extraer este código en una función de macro. Llamemos a la macro file_generator
, que tiene los parámetros name
y arg
. Reemplaza la genrule con lo siguiente:
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",
)
Aquí, se carga el símbolo file_generator
desde un archivo .bzl
ubicado en el paquete //path
. Cuando colocas definiciones de funciones macro en un archivo .bzl
independiente, mantienes tus archivos BUILD
limpios y declarativos. El archivo .bzl
se puede cargar desde cualquier paquete en el lugar de trabajo.
Por último, en path/generator.bzl
, escribe la definición de la macro para encapsular y parametrizar la definición original de genrule:
def file_generator(name, arg, visibility=None):
native.genrule(
name = name,
outs = [name + ".txt"],
cmd = "$(location //:generator) %s > $@" % arg,
tools = ["//:generator"],
visibility = visibility,
)
También puede usar macros para encadenar reglas. En este ejemplo, se muestran genrules encadenadas, en las que una genrule usa los resultados de una genrule anterior como entradas:
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,
)
En el ejemplo, solo se asigna un valor de visibilidad a la segunda genrule. Esto permite que los autores de macros oculten los resultados de las reglas intermedias para que otros destinos del lugar de trabajo no dependan de ellas.
Macros desplegables
Cuando desees investigar qué hace una macro, usa el comando query
con --output=build
para ver el formulario expandido:
$ 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 > $@",
)
Cómo crear instancias de reglas nativas
Se pueden crear instancias de las reglas nativas (reglas que no necesitan una declaración load()
) desde el módulo native:
def my_macro(name, visibility=None):
native.cc_library(
name = name,
srcs = ["main.cc"],
visibility = visibility,
)
Si necesitas conocer el nombre del paquete (por ejemplo, qué archivo BUILD
llama a la macro), usa la función native.package_name(). Ten en cuenta que native
solo se puede usar en archivos .bzl
, no en archivos WORKSPACE
o BUILD
.
Resolución de etiquetas en macros
Dado que las macros se evalúan en la fase de carga, las strings de etiquetas, como "//foo:bar"
, que se producen en una macro se interpretan en relación con el archivo BUILD
en el que se usa la macro en lugar del relativa al archivo .bzl
en el que se define. Por lo general, este comportamiento no es recomendable para las macros que se deben usar en otros repositorios, como las que forman parte de un conjunto de reglas de Starlark publicado.
A fin de obtener el mismo comportamiento que para las reglas de Starlark, une las strings de etiquetas con el constructor Label
:
# @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,
)
Depuración
bazel query --output=build //my/path:all
te mostrará el aspecto del archivoBUILD
después de la evaluación. Se expanden todas las macros, globs y bucles. Limitación conocida: Actualmente, las expresionesselect
no se muestran en el resultado.Puedes filtrar el resultado en función de
generator_function
(que es la función que generó las reglas) o degenerator_name
(el atributo de nombre de la macro):bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'
Para saber dónde se genera exactamente la regla
foo
en un archivoBUILD
, puedes probar el siguiente truco. Inserta esta línea cerca de la parte superior del archivoBUILD
:cc_library(name = "foo")
. Ejecuta Bazel. Obtendrás una excepción cuando se cree la reglafoo
(debido a un conflicto de nombres), que te mostrará el seguimiento de pila completo.También puedes usar print para la depuración. Muestra el mensaje como una línea de registro
DEBUG
durante la fase de carga. Excepto en casos excepcionales, quita las llamadasprint
o hazlas condicionales en un parámetrodebugging
que tenga el valor predeterminadoFalse
antes de enviar el código al depósito.
Errores
Si deseas generar un error, usa la función fail.
Explícale claramente al usuario qué salió mal y cómo corregir el archivo BUILD
.
No es posible detectar un error.
def my_macro(name, deps, visibility=None):
if len(deps) < 2:
fail("Expected at least two values in deps")
# ...
Convenciones
Todas las funciones públicas (funciones que no comienzan con guion bajo) que crean instancias de reglas deben tener un argumento
name
. Este argumento no debe ser opcional (no proporciones un valor predeterminado).Las funciones públicas deben usar una docstring según las convenciones de Python.
En los archivos
BUILD
, el argumentoname
de las macros debe ser un argumento de palabra clave (no un argumento posicional).El atributo
name
de las reglas generadas por una macro deben incluir el argumento de nombre como un prefijo. Por ejemplo,macro(name = "foo")
puede generar uncc_library
foo
y una genrulefoo_gen
.En la mayoría de los casos, los parámetros opcionales deben tener un valor predeterminado de
None
.None
se puede pasar directamente a las reglas nativas, lo que la trata igual que si no se hubiera pasado en ningún argumento. Por lo tanto, no es necesario reemplazarla con0
,False
o[]
para este propósito. En su lugar, la macro debe diferir a las reglas que crea, ya que sus valores predeterminados pueden ser complejos o cambiar con el tiempo. Además, un parámetro que se establece de forma explícita en su valor predeterminado se ve diferente a uno que nunca se configura (o se establece enNone
) cuando se accede a él a través del lenguaje de consulta o de las internas del sistema de compilación.Las macros deben tener un argumento
visibility
opcional.