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 no arquivo BUILD
que pode instanciar regras.
Elas são usadas principalmente para encapsulamento e reutilização de código de regras
existentes e outras macros. Ao final da
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 regra geral 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 você quiser gerar mais arquivos com argumentos diferentes, extraia
esse código para uma função de macro. Vamos chamar a macro de 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ção de macro em um arquivo
.bzl
separado, você mantém seus 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 de genrule 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 genrules encadeadas, em que uma regra geral usa as saídas de uma regra de geração 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 regra de geração. Isso permite que os autores de macro ocultem as saídas de regras intermediárias para que não dependam de outros alvos 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 formato 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 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 WORKSPACE
ou BUILD
.
Resolução de marcadores em macros
Como as macros são avaliadas na fase de carregamento,
as strings de rótulo, como "//foo:bar"
, que ocorrem em uma macro 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 está definida. Geralmente, esse comportamento não é desejável
para macros que precisam ser usadas em outros repositórios, porque fazem parte
de um conjunto de regras Starlark publicado.
Para ter o mesmo comportamento das regras do Starlark, una 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 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 arquivoBUILD
ficará após a avaliação. Todas as macros, globs e loops estão expandidos. Limitação conhecida: as expressõesselect
não são mostradas na saída.É possível filtrar a saída com base em
generator_function
(que função gerou 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 exatamente onde a regra
foo
é gerada em um arquivoBUILD
, tente o truque a seguir. Insira esta linha perto da parte de cima do arquivoBUILD
:cc_library(name = "foo")
. Execute o Bazel. Você receberá uma exceção quando a regrafoo
for criada (devido a um conflito de nomes), que mostrará o 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 casos raros, remova as chamadasprint
ou torne-as condicionais em um parâmetrodebugging
que tem como padrãoFalse
antes de enviar o código ao 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 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 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 de acordo com as convenções do Python (em inglês).
Em arquivos
BUILD
, o argumentoname
das macros precisa ser um argumento de palavra-chave, e não de posição.O atributo
name
das regras geradas por uma macro precisa incluir o argumento de nome como prefixo. Por exemplo,macro(name = "foo")
pode gerar umacc_library
foo
e umafoo_gen
de regra geral.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 como se você não tivesse transmitido nenhum argumento. Portanto, não é necessário substituí-lo por0
,False
ou[]
para essa finalidade. Em vez disso, a macro precisa seguir as regras criadas, já que os padrões podem ser complexos ou mudar com o tempo. Além disso, um parâmetro definido explicitamente com o valor padrão parece diferente de um que nunca é definido (ou definido comoNone
) quando acessado por meio da linguagem de consulta ou dos componentes internos do sistema de build.As macros precisam ter um argumento
visibility
opcional.