Macros

Informar um problema Mostrar fonte Por noite · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Esta página aborda os conceitos básicos do uso de macros e inclui casos de uso típicos, depuração e convenções.

Uma macro é uma função chamada pelo arquivo BUILD que pode instanciar regras. Macros são usadas principalmente para encapsulamento e reutilização de código de regras existentes e outras macros. Ao final do fase de carregamento, as macros não existem mais, e o Bazel vê apenas o conjunto concreto de regras instanciadas.

Uso

O caso de uso típico de uma macro é quando você quer reutilizar uma regra.

Por exemplo, a genrule em um arquivo BUILD gera um arquivo usando //:generator com um argumento some_arg fixado no código no comando:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

Se quiser gerar mais arquivos com argumentos diferentes, extraia esse código para uma função de macro. Vamos chamar a macro file_generator, que tem os parâmetros name e arg. Substitua a regra geral pelo seguinte:

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",
)

Aqui, você carrega o símbolo file_generator de um arquivo .bzl localizado no pacote //path. Ao colocar definições de funções de macro em um .bzl, mantenha os arquivos BUILD limpos e declarativos, o .bzl pode ser carregado de qualquer pacote no espaço de trabalho.

Por fim, em path/generator.bzl, grave a definição da macro no encapsular e parametrizar a definição da regra geral original:

def file_generator(name, arg, visibility=None):
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location //:generator) %s > $@" % arg,
    tools = ["//:generator"],
    visibility = visibility,
  )

Também é possível usar macros para encadear regras. Este exemplo mostra dados encadeados regras gerais, em que uma regra geral usa as saídas de uma regra 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,
  )

O exemplo atribui um valor de visibilidade apenas à segunda genrule. Isso permite autores de macro para ocultar as saídas das regras intermediárias para que não dependam das por outros destinos no espaço de trabalho.

Macros de expansão

Quando você quiser investigar o que uma macro faz, use o comando query com --output=build para ver o formulário 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 > $@",
)

Como instanciar regras nativas

As regras nativas (que não precisam de uma instrução load()) podem ser instanciado do módulo native:

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

Se você precisar saber o nome do pacote (por exemplo, qual arquivo BUILD está chamando a ), use a função native.package_name(). native só pode ser usado em arquivos .bzl, e não em WORKSPACE ou BUILD.

Resolução de rótulos em macros

Como as macros são avaliadas na fase de carregamento, strings de rótulos como "//foo:bar" que ocorrem em uma macro são interpretadas relativo ao arquivo BUILD no qual a macro é usada, e não relativa ao no arquivo .bzl em que ele é definido. Esse comportamento é geralmente indesejável para macros que se destinam ao uso em outros repositórios, como porque eles fazem parte de um conjunto de regras Starlark publicado.

Para obter o mesmo comportamento das regras do Starlark, una as strings do rótulo com o Construtor 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,
  )

Depuração

  • bazel query --output=build //my/path:all vai mostrar como o arquivo BUILD é responsável pela avaliação. Todas as macros, globs e loops são expandidos. Conhecidos Limitação: no momento, as expressões select não são exibidas na saída.

  • É possível filtrar a saída com base em generator_function (que função gerou as regras) ou generator_name (o atributo de nome da macro): bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'

  • Para descobrir onde exatamente a regra foo é gerada em um arquivo BUILD, pode tentar o truque a seguir. Insira esta linha perto da parte de cima do BUILD. arquivo: cc_library(name = "foo"). Execute o Bazel. Você vai receber uma exceção quando a regra foo for criada (devido a um conflito de nomes), o que mostrará a você os stack trace completo.

  • Também é possível usar print para depuração. Ela mostra a mensagem como uma linha de registro DEBUG durante a fase de carregamento. Exceto em raras ocasiões casos, remover chamadas print ou torná-las condicionais em uma Parâmetro debugging que assume o padrão False antes de enviar o código para no depósito.

Erros

Se você quiser gerar um erro, use a função fail. Explique claramente ao usuário o que deu errado e como corrigir o arquivo BUILD. Não é possível capturar um erro.

def my_macro(name, deps, visibility=None):
  if len(deps) < 2:
    fail("Expected at least two values in deps")
  # ...

Convenções

  • Todas as funções públicas (funções que não começam com sublinhado) que as regras de instanciação precisam ter um argumento name. Esse argumento não deve ser opcional (não forneça um valor padrão).

  • As funções públicas devem usar um docstring seguindo o Python comuns.

  • Em arquivos BUILD, o argumento name das macros precisa ser uma palavra-chave (não um argumento posicional).

  • O atributo name das regras geradas por uma macro precisa incluir o nome como prefixo. Por exemplo, macro(name = "foo") pode gerar uma cc_library foo e uma regra geral foo_gen.

  • Na maioria dos casos, os parâmetros opcionais precisam ter um valor padrão de None. None pode ser transmitido diretamente para regras nativas, que o tratam da mesma forma que se fossem você não transmitiu nenhum argumento. Por isso, não é necessário substituí-lo com 0, False ou [] para essa finalidade. Em vez disso, a macro precisa adiar às regras que ele cria, já que os padrões podem ser complexos ou mudar ao longo do tempo de resposta. Além disso, um parâmetro que é definido explicitamente com seu valor padrão parece diferente daquele que nunca é definido (ou definido como None) quando acessado. usando a linguagem de consulta ou elementos internos do sistema de build.

  • As macros precisam ter um argumento visibility opcional.