As macros legadas são funções não estruturadas chamadas de arquivos BUILD
que podem criar destinos. Ao final da fase de carregamento, as macros legadas não existem mais, e o Bazel vê apenas 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 ocultar detalhes de implementação com visibilidade granular
- Usar atributos digitados, o que significa conversão automática de rótulos e seleção.
- São mais legíveis
- Em breve, teremos avaliação lenta.
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
codificado 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. 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 de 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 da 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 genrules encadeadas, em que uma genrule usa as saídas de uma 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,
)
O exemplo atribui um valor de visibilidade apenas à segunda genrule. Isso permite que autores de macros ocultem as saídas de regras intermediárias para que não sejam usadas por outros destinos no espaço de trabalho.
Expansão de macros
Quando 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 (que não precisam de uma instrução load()
) podem ser instanciadas
do módulo nativas:
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, 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, e não em relação ao arquivo .bzl
em que ela é definida. Esse comportamento geralmente é indesejável para macros que
devem ser usadas em outros repositórios, por exemplo, porque fazem parte de um
conjunto de regras Starlark publicado.
Para ter o mesmo comportamento das regras do Starlark, encapsule 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
bazel query --output=build //my/path:all
vai mostrar como o arquivoBUILD
fica após a avaliação. Todas as macros, globs e loops legados são expandidos. Limitação conhecida: as expressõesselect
não aparecem na saída.Você pode filtrar a saída com base em
generator_function
(qual função gerou as regras) ougenerator_name
(o atributo "name" 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 perto da parte de cima do arquivoBUILD
:cc_library(name = "foo")
. Execute o Bazel. Você vai receber uma exceção quando a regrafoo
for criada (devido a um conflito de nomes), que vai mostrar o rastreamento de pilha completo.Você também pode usar print para depuração. Ela mostra a mensagem como uma linha de registro
DEBUG
durante a fase de carregamento. Exceto em casos raros, remova as chamadasprint
ou torne-as condicionais em um parâmetrodebugging
que tenha como padrãoFalse
antes de enviar o código ao repositório.
Erros
Se 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 (que não começam com sublinhado) que instanciam regras precisam ter um argumento
name
. Esse argumento não pode ser opcional (não forneça um valor padrão).As funções públicas precisam usar uma docstring seguindo as convenções do Python.
Nos arquivos
BUILD
, o argumentoname
das macros precisa ser um argumento de palavra-chave (não posicional).O atributo
name
das regras geradas por uma macro precisa incluir o argumento "name" como um prefixo. Por exemplo,macro(name = "foo")
pode gerar umcc_library
foo
e uma genrulefoo_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 você não tivesse transmitido nenhum argumento. Portanto, não é necessário substituir por0
,False
ou[]
para essa finalidade. Em vez disso, a macro deve obedecer às regras que cria, já que os padrões delas podem ser complexos ou mudar com o tempo. Além disso, um parâmetro definido explicitamente com o valor padrão tem uma aparência diferente de um que nunca foi definido (ou definido comoNone
) quando acessado pela linguagem de consulta ou pelos internos do sistema de build.As macros precisam ter um argumento
visibility
opcional.