Macros legados são funções não estruturadas chamadas de arquivos BUILD que podem
criar destinos. No final da
fase de carregamento, as macros legadas não existem
mais, e o Bazel só encontra o conjunto concreto de regras instanciadas.
Por que não usar macros legadas (e usar macros simbólicas)
Sempre que possível, use macros simbólicas.
Macros simbólicas
- Impedir ações à distância
- Permitir que os detalhes de implementação sejam ocultados com visibilidade granular
- Use atributos digitados, que significam rótulo automático e conversão selecionada.
- sejam mais legíveis.
- Em breve, vai ter avaliação lenta
Uso
O caso de uso típico de uma macro é quando você quer reutilizar uma regra.
Por exemplo, o genrule em um arquivo BUILD gera um arquivo usando //:generator
com um argumento some_arg codificado no comando:
genrule(
name = "file",
outs = ["file.txt"],
cmd = "$(location //:generator) some_arg > $@",
tools = ["//:generator"],
)
Se você quiser gerar mais arquivos com argumentos diferentes, extraia esse código para uma função de macro. Para criar uma macro chamada
file_generator, que tem parâmetros name e arg, podemos substituir a
genrule 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 as definições de função macro em um arquivo .bzl
separado, você mantém os arquivos BUILD limpos e declarativos. O arquivo .bzl pode ser
carregado de qualquer pacote no espaço de trabalho.
Por fim, em path/generator.bzl, escreva a definição da macro para
encapsular e parametrizar a definição 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,
)
Você também pode usar macros para encadear regras. Este exemplo mostra regras genéricas conectadas, em que uma regra genérica usa as saídas de uma regra genérica 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 só atribui um valor de visibilidade à segunda regra de geração. Isso permite que os autores de macros ocultem as saídas de regras intermediárias para que não sejam dependentes de outras metas no espaço de trabalho.
Macros expandidas
Quando você quiser investigar o que uma macro faz, use o comando query com
--output=build para ver a 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 > $@",
)
Como instanciar regras nativas
As regras nativas (regras que não precisam de uma instrução load()) podem ser instanciadas
no 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 macro), use a função
native.package_name(). Observe que
native só pode ser usado em arquivos .bzl, e não em arquivos BUILD.
Resolução de rótulos em macros
Como as macros legadas são avaliadas na
fase de carregamento, as strings de rótulo, como
"//foo:bar", que ocorrem em uma macro legada são interpretadas em relação ao
arquivo BUILD em que a macro é usada, em vez de em relação ao arquivo .bzl
em que ela é definida. Esse comportamento geralmente é indesejável para macros que
são usadas em outros repositórios, por exemplo, porque fazem parte de um
conjunto de regras do Starlark publicado.
Para ter o mesmo comportamento das regras do Starlark, envolva as strings de 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 repo, such as due to repo mappings.
Label("@dep_of_my_ruleset//tools:foo"),
],
"//conditions:default": [],
}),
**kwargs,
)
Depuração
O
bazel query --output=build //my/path:allvai mostrar como o arquivoBUILDfica após a avaliação. Todas as macros, globs e loops legados são expandidos. Limitação conhecida: as expressõesselectnão são mostradas na saída.É possível filtrar a saída com base em
generator_function(que gera as regras) ougenerator_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 arquivoBUILD, tente o seguinte truque. Insira esta linha na parte de cima do arquivoBUILD:cc_library(name = "foo"). Execute o Bazel. Você vai receber uma exceção quando a regrafoofor criada (devido a um conflito de nome), que vai mostrar o stack trace completo.Também é possível usar print para depuração. Ela mostra a mensagem como uma linha de registro
DEBUGdurante a fase de carregamento. Exceto em casos raros, remova as chamadasprintou torne-as condicionais em um parâmetrodebuggingque é padrão paraFalseantes de enviar o código para o repositório.
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 detectar 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 instanciam regras precisam ter um argumento
name. Esse argumento não pode ser opcional (não dê um valor padrão).As funções públicas precisam usar uma docstring seguindo as convenções do Python.
Em arquivos
BUILD, o argumentonamedas macros precisa ser um argumento de palavra-chave, não um argumento posicional.O atributo
namedas regras geradas por uma macro precisa incluir o argumento de nome como um prefixo. Por exemplo,macro(name = "foo")pode gerar umfoocc_librarye umfoo_gende genrule.Na maioria dos casos, os parâmetros opcionais precisam ter um valor padrão de
None.Nonepode ser transmitido diretamente para regras nativas, que o tratam da mesma forma que se você não tivesse transmitido nenhum argumento. Portanto, não é necessário substituí-lo por0,Falseou[]para essa finalidade. Em vez disso, a macro precisa se submeter às regras que ela cria, já que os padrões dela podem ser complexos ou mudar com o tempo. Além disso, um parâmetro definido explicitamente como o valor padrão parece diferente de um que nunca é definido (ou definido comoNone) quando acessado pela linguagem de consulta ou pelo sistema de build interno.As macros precisam ter um argumento
visibilityopcional.