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.
As macros são usadas principalmente para encapsulamento e reutilização de código de regras e
outras macros.
Há duas opções de macros: macros simbólicas, descritas nesta página, e macros legadas. Sempre que possível, recomendamos o uso de macros simbólicas para maior clareza do código.
As macros simbólicas oferecem argumentos com tipo (string para conversão de rótulo, em relação ao
local em que a macro foi chamada) e a capacidade de restringir e especificar a
visibilidade dos destinos criados. Eles foram projetados para serem suscetíveis a avaliação
preguiçosa (que será adicionada em uma versão futura do Bazel). Macros simbólicas estão
disponíveis por padrão no Bazel 8. Quando este documento menciona macros
, ele
se refere a macros simbólicos.
Uso
As macros são definidas em arquivos .bzl
chamando a função macro()
com dois parâmetros: attrs
e implementation
.
Atributos
attrs
aceita um dicionário de nome de atributo para tipos de
atributos, que representa
os argumentos para a macro. Dois atributos comuns, nome e visibilidade, são
adicionados implicitamente a todas as macros e não são incluídos no dicionário transmitido para
atributos.
# macro/macro.bzl
my_macro = macro(
attrs = {
"deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
"create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
},
implementation = _my_macro_impl,
)
As declarações de tipo de atributo aceitam os
parâmetros
mandatory
, default
e doc
. A maioria dos tipos de atributo também aceita o
parâmetro configurable
, que determina se o atributo aceita
select
s. Se um atributo for configurable
, ele analisará valores que não são select
como um select
não configurável. "foo"
vai se tornar
select({"//conditions:default": "foo"})
. Saiba mais nas seleções.
Implementação
implementation
aceita uma função que contém a lógica da macro.
As funções de implementação geralmente criam destinos chamando uma ou mais regras e
geralmente são particulares (nomeadas com um sublinhado inicial). Convencionalmente,
elas são nomeadas com o mesmo nome da macro, mas têm o prefixo _
e
_impl
como sufixo.
Ao contrário das funções de implementação de regras, que usam um único argumento (ctx
) que
contém uma referência aos atributos, as funções de implementação de macros aceitam um
parâmetro para cada argumento.
# macro/macro.bzl
def _my_macro_impl(name, deps, create_test):
cc_library(
name = name + "_cc_lib",
deps = deps,
)
if create_test:
cc_test(
name = name + "_test",
srcs = ["my_test.cc"],
deps = deps,
)
Declaração
As macros são declaradas carregando e chamando sua definição em um arquivo BUILD
.
# pkg/BUILD
my_macro(
name = "macro_instance",
deps = ["src.cc"] + select(
{
"//config_setting:special": ["special_source.cc"],
"//conditions:default": [],
},
),
create_tests = True,
)
Isso criaria destinos
//pkg:macro_instance_cc_lib
e//pkg:macro_instance_test
.
Detalhes
convenções de nomenclatura para segmentações criadas
Os nomes dos destinos ou submacros criados por uma macro simbólica precisam
corresponder ao parâmetro name
da macro ou ter o prefixo name
seguido
por _
(preferencial), .
ou -
. Por exemplo, my_macro(name = "foo")
só pode criar arquivos ou destinos com o nome foo
ou prefixado por foo_
, foo-
ou foo.
, por exemplo, foo_bar
.
É possível declarar destinos ou arquivos que violam a convenção de nomenclatura de macros, mas eles não podem ser criados nem usados como dependências.
Os arquivos e destinos que não são macros no mesmo pacote de uma instância de macro não podem ter nomes que conflitam com possíveis nomes de destino de macro, embora essa exclusividade não seja aplicada. Estamos em processo de implementação da avaliação lenta como uma melhoria de desempenho para macros simbólicas, que serão prejudicadas em pacotes que violam o esquema de nomenclatura.
restrições
As macros simbólicas têm algumas restrições adicionais em comparação com as macros legadas.
Macros simbólicas
- precisa receber um argumento
name
e um argumentovisibility
- precisa ter uma função
implementation
- não pode retornar valores
- não pode mudar o
args
- não podem chamar
native.existing_rules()
, a menos que sejam macrosfinalizer
especiais - pode não ligar para
native.package()
- pode não ligar para
glob()
- não pode ligar para
native.environment_group()
- precisa criar destinos cujos nomes aderem ao esquema de nomenclatura
- não pode se referir a arquivos de entrada que não foram declarados ou transmitidos como um argumento (consulte visibilidade para mais detalhes).
Visibilidade
O que fazer: expandir esta seção
Visibilidade desejada
Por padrão, os destinos criados por macros simbólicas são visíveis para o pacote em
que são criados. Elas também aceitam um atributo visibility
, que pode
expandir essa visibilidade para o autor da chamada da macro (transmitindo o atributo visibility
diretamente da chamada da macro para o destino criado) e para outros
pacotes (especificando-os explicitamente na visibilidade do destino).
Visibilidade da dependência
As macros devem ter visibilidade dos arquivos e destinos aos quais se referem. Isso pode ser feito das seguintes maneiras:
- Transmitido explicitamente como um valor
attr
para a macro.
# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
- Padrão implícito de um valor
attr
# my_macro:macro.bzl
my_macro = macro(
attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
- Já está visível para a definição da macro
# other_package/BUILD
cc_binary(
name = "my_tool",
visibility = "//my_macro:\\__pkg__",
)
Seleciona
Se um atributo for configurable
, a função de implementação de macro sempre vai
encontrar o valor do atributo como select
. Por exemplo, considere a
seguinte macro:
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
Se my_macro
for invocado com deps = ["//a"]
, _my_macro_impl
será invocado com o parâmetro deps
definido como select({"//conditions:default":
["//a"]})
.
Os destinos de regra revertem essa transformação e armazenam select
s triviais como
valores condicionais. Neste exemplo, se _my_macro_impl
declarar um destino
de regra my_rule(..., deps = deps)
, o deps
desse destino será armazenado como
["//a"]
.
Finalizadores
Um finalizador de regra é uma macro simbólica especial que, independentemente da posição
lexical em um arquivo BUILD, é avaliada na fase final do carregamento de um pacote,
depois que todas as metas não finalizadoras são definidas. Ao contrário das macros simbólicas
comuns, um finalizador pode chamar native.existing_rules()
, em que ele se comporta
de maneira um pouco diferente do que nas macros legadas: ele retorna apenas o conjunto de
destinos de regras não finalizadores. O finalizador pode declarar o estado desse conjunto ou
definir novos alvos.
Para declarar um finalizador, chame macro()
com finalizer = True
:
def _my_finalizer_impl(name, visibility, tags_filter):
for r in native.existing_rules().values():
for tag in r.get("tags", []):
if tag in tags_filter:
my_test(
name = name + "_" + r["name"] + "_finalizer_test",
deps = [r["name"]],
data = r["srcs"],
...
)
continue
my_finalizer = macro(
attrs = {"tags_filter": attr.string_list(configurable = False)},
implementation = _impl,
finalizer = True,
)
Preguiça
IMPORTANTE: estamos em processo de implementação de expansão e avaliação lentas de macros. Este recurso ainda não está disponível.
Atualmente, todas as macros são avaliadas assim que o arquivo BUILD é carregado, o que pode afetar negativamente o desempenho de destinos em pacotes que também têm macros não relacionadas caras. No futuro, as macros simbólicas não de finalização só serão avaliadas se forem necessárias para o build. O esquema de nomenclatura de prefixo ajuda o Bazel a determinar qual macro será expandida com base em um destino solicitado.
Solução de problemas de migração
Confira algumas dores de cabeça comuns na migração e como corrigi-las.
- Chamadas de macro legada
glob()
Mova a chamada glob()
para o arquivo BUILD (ou para uma macro legada chamada do arquivo BUILD) e transmita o valor glob()
para a macro simbólica usando um atributo de lista de rótulos:
# BUILD file
my_macro(
...,
deps = glob(...),
)
- A macro legada tem um parâmetro que não é um tipo
attr
válido do Starlark.
Use o máximo de lógica possível em uma macro simbólica aninhada, mas mantenha a macro de nível superior como uma macro legada.
- A macro legada chama uma regra que cria um destino que viola o esquema de nomenclatura
Tudo bem, mas não dependa do alvo "ofensivo". A verificação de nomenclatura será ignorada silenciosamente.