Las macros heredadas son funciones no estructuradas que se llaman desde archivos BUILD que pueden
crear objetivos. Al final de la
fase de carga, las macros heredadas ya no existen
más, y Bazel solo ve el conjunto concreto de reglas de instancias.
Por qué no deberías usar macros heredadas (y deberías usar macros simbólicas)
Siempre que sea posible, debes usar macros simbólicas.
Macros simbólicas
- Impiden la acción a distancia.
- Permiten ocultar los detalles de implementación a través de la visibilidad detallada.
- Toman atributos tipados, lo que a su vez significa la conversión automática de etiquetas y selecciones
- Son más legibles.
- Pronto tendrán evaluación diferida.
Uso
El caso de uso típico de una macro es cuando deseas reutilizar una regla.
Por ejemplo, genrule en un archivo BUILD genera un archivo con //: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, es posible que desees
extraer este código a una función de macro. Para crear una macro llamada
file_generator, que tiene parámetros name y arg, podemos reemplazar la
genrule por 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í, cargas el símbolo file_generator desde un archivo .bzl ubicado en el
//path paquete. Si colocas las definiciones de funciones de macro en un archivo .bzl
separado, mantendrás tus archivos BUILD limpios y declarativos. El archivo .bzl se puede
cargar desde cualquier paquete en el espacio 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 puedes 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 no dependan de otros objetivos en el espacio de trabajo.
Expansión de macros
Cuando desees investigar qué hace una macro, usa el query comando 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 > $@",
)
Creación de instancias de reglas nativas
Las reglas nativas (reglas que no necesitan una instrucción load()) se pueden crear instancias
desde el módulo nativo:
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 y no en archivos BUILD.
Resolución de etiquetas en macros
Dado que las macros heredadas se evalúan en la
fase de carga, las cadenas de etiquetas como
"//foo:bar" que aparecen en una macro heredada se interpretan en relación con el archivo
BUILD en el que se usa la macro en lugar de en relación con el archivo .bzl
en el que se define. Por lo general, este comportamiento no es deseable para las macros que
se deben usar en otros repositorios, como porque forman parte de un
conjunto de reglas de Starlark publicado.
Para obtener el mismo comportamiento que para las reglas de Starlark, incluye las cadenas de etiquetas con el
Label constructor:
# @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 repo, such as due to repo mappings.
Label("@dep_of_my_ruleset//tools:foo"),
],
"//conditions:default": [],
}),
**kwargs,
)
Con la marca --incompatible_eagerly_resolve_select_keys habilitada, todas las claves
que son cadenas de etiquetas se resolverán automáticamente en objetos Label en relación con el paquete del archivo que contiene la llamada select. Si no se elige esta opción, incluye la cadena de etiquetas con
native.package_relative_label().
Depuración
bazel query --output=build //my/path:allte mostrará cómo se ve el archivoBUILDdespués de la evaluación. Se expanden todas las macros, los globs y los bucles heredados. Limitación conocida: Las expresionesselectno se muestran en el resultado.Puedes filtrar el resultado según
generator_function(qué función generó las reglas) ogenerator_name(el atributo name de la macro):bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'Para averiguar dónde se genera exactamente la regla
fooen un archivoBUILD, puedes probar el siguiente truco. Inserta esta línea cerca de la parte superior delBUILDarchivo: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
DEBUGdurante la fase de carga. Except en casos excepcionales, quita las llamadasprinto hazlas condicionales en un parámetrodebuggingque se establezca de forma predeterminada enFalseantes de enviar el código a l 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 su
BUILD archivo. 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 un 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 cadena de documentación que siga las convenciones de Python.
En los archivos
BUILD, el argumentonamede las macros debe ser un argumento de palabra clave (no un argumento posicional).El atributo
namede las reglas generadas por una macro debe incluir el argumento name como prefijo. Por ejemplo,macro(name = "foo")puede generar unacc_libraryfooy una genrulefoo_gen.En la mayoría de los casos, los parámetros opcionales deben tener un valor predeterminado de
None.Nonese puede pasar directamente a las reglas nativas, que lo tratan como si no hubieras pasado ningún argumento. Por lo tanto, no es necesario reemplazarlo por0,False, o[]para este propósito. En cambio, 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 explícitamente en su valor predeterminado se ve diferente de uno que nunca se establece (o se establece enNone) cuando se accede a través del lenguaje de consulta o los elementos internos del sistema de compilación.Las macros deben tener un argumento
visibilityopcional.