Macros heredadas

Informar un problema Ver código fuente Nocturno · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Las macros heredadas son funciones no estructuradas que se llaman desde archivos BUILD y pueden crear destinos. Al final de la fase de carga, las macros heredadas ya no existen y Bazel solo ve el conjunto concreto de reglas instanciadas.

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

  • Evita la acción a distancia
  • Permite ocultar los detalles de implementación a través de la visibilidad detallada
  • Toma atributos escritos, lo que a su vez significa conversión automática de etiquetas y selecciones.
  • Son más legibles.
  • Pronto tendrá 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 y un argumento some_arg codificado de forma rígida 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 a una función de macro. Para crear una macro llamada file_generator, que tiene parámetros name y arg, podemos reemplazar 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 paquete //path. 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 del 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 regla genrule. Esto permite que los autores de macros oculten los resultados de las reglas intermedias para que otros destinos del espacio de trabajo no dependan de ellos.

Macros de expansión

Cuando quieras investigar qué hace una macro, usa el comando query con --output=build para ver la forma expandida:

$ 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

Las reglas nativas (las que no necesitan una instrucción load()) se pueden crear instancias desde el módulo native:

def my_macro(name, visibility=None):
  native.cc_library(
    name = name,
    srcs = ["main.cc"],
    visibility = visibility,
  )

Si necesitas saber 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 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, por ejemplo, porque forman parte de un conjunto de reglas de Starlark publicado.

Para obtener el mismo comportamiento que para las reglas de Starlark, une las cadenas 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 repo, 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á cómo se ve el archivo BUILD después de la evaluación. Se expanden todas las macros, los comodines y los bucles heredados. Limitación conocida: Las expresiones de select no se muestran en el resultado.

  • Puedes filtrar el resultado según generator_function (qué función generó las reglas) o generator_name (el atributo name 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 archivo BUILD, puedes probar el siguiente truco. Inserta esta línea cerca de la parte superior del archivo BUILD: cc_library(name = "foo"). Ejecuta Bazel. Recibirás una excepción cuando se cree la regla foo (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 llamadas a print o haz que sean condicionales con un parámetro debugging que tenga el valor predeterminado False antes de enviar el código al depósito.

Errores

Si quieres arrojar un error, usa la función fail. Explícale claramente al usuario qué salió mal y cómo solucionar el problema con su 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 (las 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 argumento name de las macros debe ser un argumento de palabra clave (no un argumento posicional).

  • El atributo name de las reglas generadas por una macro debe incluir el argumento name como prefijo. Por ejemplo, macro(name = "foo") puede generar un cc_library foo y una regla genrule foo_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, que lo tratan como si no hubieras pasado ningún argumento. Por lo tanto, no es necesario reemplazarlo por 0, 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 en None) cuando se accede a él a través del lenguaje de consultas o los elementos internos del sistema de compilación.

  • Las macros deben tener un argumento visibility opcional.