Esta página aborda os benefícios e o uso básico das configurações do Starlark, API do Bazel para personalizar a compilação do projeto. Ele inclui como definir e fornece exemplos.
Isso possibilita:
- definir sinalizações personalizadas para seu projeto, obsoletos
--define
- gravação
transições para configurar dependências em
configurações diferentes das dos pais
(como
--compilation_mode=opt
ou--cpu=arm
) - incorporar padrões melhores em regras, como a criação automática de
//my:android_app
. com um SDK especificado)
e muito mais, usando arquivos .bzl, sem precisar de uma versão do Bazel. Consulte a
bazelbuild/examples
repositório para
exemplos.
Configurações de build definidas pelo usuário
Uma configuração de criação é uma parte
configuração
informações imprecisas ou inadequadas. Pense em uma configuração como um mapa de chave-valor. Configurando --cpu=ppc
e --copt="-DFoo"
produz uma configuração parecida com
{cpu: ppc, copt: "-DFoo"}
Cada entrada é uma configuração de build.
As sinalizações tradicionais, como cpu
e copt
, são configurações nativas.
as chaves são definidas e os valores são definidos no código Java Bazel nativo.
Os usuários do Bazel só podem ler e gravar na linha de comando.
e outras APIs mantidas de modo nativo. Como alterar sinalizações nativas e as APIs
que os expõem, exigem uma versão do Bazel. Build definido pelo usuário
são definidas em arquivos .bzl
(e, portanto, não precisam de uma versão do Bazel para
registrar alterações). Eles também podem ser definidos na linha de comando
(se estiverem designados como flags
, veja mais abaixo), mas também podem ser
usando transições definidas pelo usuário.
Como definir configurações de build
O parâmetro build_setting
rule()
As configurações de criação são regras como qualquer outra regra e são diferenciadas usando o
build_setting
da função rule()
do Starlark
atributo.
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
O atributo build_setting
usa uma função que designa o tipo do
do build. O tipo é limitado a um conjunto de tipos básicos de Starlark, como
bool
e string
. Consulte o módulo config
.
documentação para mais detalhes. Uma digitação mais complicada pode ser
feito na função de implementação da regra. Mais informações sobre isso abaixo.
As funções do módulo config
usam um parâmetro booleano opcional, flag
,
que é definido como falso por padrão. se flag
for definido como verdadeiro, a configuração de build
podem ser definidas na linha de comando pelos usuários e internamente pelos criadores de regras
usando valores padrão e transições.
Nem todas as configurações devem ser definidas pelos usuários. Por exemplo, se você, como regra,
editor tem algum modo de depuração que você gostaria de ativar dentro das regras de teste,
não dê aos usuários a capacidade de ativá-los indiscriminadamente
dentro de outras regras que não sejam de teste.
Como usar ctx.build_setting_value
Como todas as regras, as de configuração de build têm funções de implementação.
O valor básico do tipo Starlark das configurações de compilação pode ser acessado pelo
ctx.build_setting_value
. Esse método está disponível apenas para
Objetos ctx
das regras de configuração do build. Essas implementações
podem encaminhar diretamente o valor das configurações de compilação ou realizar trabalhos adicionais
como verificação de tipo ou criação de structs mais complexos. Veja como você faria
implementar uma configuração de build do tipo enum
:
# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])
temperatures = ["HOT", "LUKEWARM", "ICED"]
def _impl(ctx):
raw_temperature = ctx.build_setting_value
if raw_temperature not in temperatures:
fail(str(ctx.label) + " build setting allowed to take values {"
+ ", ".join(temperatures) + "} but was set to unallowed value "
+ raw_temperature)
return TemperatureProvider(type = raw_temperature)
temperature = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
Como definir sinalizações de string com vários conjuntos
As configurações de string têm um parâmetro allow_multiple
extra que permite
a ser definida várias vezes na linha de comando ou em bazelrcs. O padrão da pessoa
value ainda é definido com um atributo do tipo string:
# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/buildsettings/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
name = "roasts",
build_setting_default = "medium"
)
Cada configuração da sinalização é tratada como um único valor:
$ bazel build //my/target --//example:roasts=blonde \
--//example:roasts=medium,dark
O código acima é analisado para {"//example:roasts": ["blonde", "medium,dark"]}
e
ctx.build_setting_value
retorna a lista ["blonde", "medium,dark"]
.
Como instanciar configurações do build
As regras definidas com o parâmetro build_setting
têm um valor obrigatório implícito
build_setting_default
. Esse atributo assume o mesmo tipo que
declarado pelo parâmetro build_setting
.
# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])
def _impl(ctx):
return FlavorProvider(type = ctx.build_setting_value)
flavor = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
# example/buildsettings/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
Configurações predefinidas
O Skylib inclui um conjunto de configurações predefinidas que você pode instanciar sem precisar para criar um Starlark personalizado.
Por exemplo, para definir uma configuração que aceite um conjunto limitado de valores de string:
# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "myflag",
values = ["a", "b", "c"],
build_setting_default = "a",
)
Para uma lista completa, consulte Regras comuns de configuração do build.
Como usar as configurações de build
Dependendo das configurações de build
Se um destino quiser ler uma parte das informações de configuração, ele poderá dependem diretamente da configuração do build usando uma dependência de atributos normal.
# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
...
drink_rule = rule(
implementation = _rule_impl,
attrs = {
"flavor": attr.label()
}
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
drink_rule(
name = "my_drink",
flavor = ":favorite_flavor",
)
As linguagens podem querer criar um conjunto canônico de configurações de build que todas as regras
dessa linguagem dependerão. Embora o conceito nativo de fragments
não
como um objeto fixado no código no mundo de configuração do Starlark, uma maneira de
esse conceito seria usar conjuntos de atributos implícitos comuns. Por
exemplo:
# kotlin/rules.bzl
_KOTLIN_CONFIG = {
"_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
"_mode": attr.label(default = "//kotlin/config:mode-flag"),
...
}
...
kotlin_library = rule(
implementation = _rule_impl,
attrs = dicts.add({
"library-attr": attr.string()
}, _KOTLIN_CONFIG)
)
kotlin_binary = rule(
implementation = _binary_impl,
attrs = dicts.add({
"binary-attr": attr.label()
}, _KOTLIN_CONFIG)
Como usar as configurações de build na linha de comando
Assim como na maioria das flags nativas, é possível usar a linha de comando para definir configurações de build
marcadas como sinalizações. O build
O nome da configuração é o caminho de destino completo usando a sintaxe name=value
:
$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed
Há suporte para sintaxe booleana especial:
$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag
Como usar aliases de configuração de build
Você pode definir um alias para o caminho de destino da configuração da versão a fim de facilitar a leitura. na linha de comando. Os aliases funcionam de maneira semelhante às sinalizações nativas e também da sintaxe da opção de dois traços.
Defina um alias adicionando --flag_alias=ALIAS_NAME=TARGET_PATH
.
ao seu .bazelrc
. Por exemplo, para definir um alias como coffee
:
# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp
Prática recomendada: definir um alias várias vezes resulta na versão mais recente a outra tenha precedência. Use nomes de alias exclusivos para evitar resultados de análise não intencionais.
Para usar o alias, digite-o no lugar do caminho de destino da configuração do build.
Com o exemplo acima de coffee
definido no .bazelrc
do usuário:
$ bazel build //my/target --coffee=ICED
em vez de
$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED
Prática recomendada: embora seja possível definir aliases na linha de comando, deixar-os
em um .bazelrc
reduz a desorganização na linha de comando.
Configurações de build com tipo de rótulo
Ao contrário de outras configurações de compilação, as configurações tipadas por rótulo não podem ser definidas usando o
Parâmetro de regra build_setting
. Em vez disso, o Bazel tem duas regras integradas:
label_flag
e label_setting
. Essas regras encaminham os provedores dos
destino real para o qual a configuração do build é definida. label_flag
e
label_setting
pode ser lido/gravado por transições e label_flag
pode ser definido
pelo usuário, assim como outras regras de build_setting
. A única diferença é que eles
não podem ser definidas de maneira personalizada.
As configurações de tipo de rótulo vão substituir a funcionalidade de limites tardios
padrão. Os atributos padrão com prazo final são atributos do tipo rótulo com
os valores finais podem ser afetados pela configuração. No Starlark, isso substituirá
configuration_field
API.
# example/rules.bzl
MyProvider = provider(fields = ["my_field"])
def _dep_impl(ctx):
return MyProvider(my_field = "yeehaw")
dep_rule = rule(
implementation = _dep_impl
)
def _parent_impl(ctx):
if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
...
parent_rule = rule(
implementation = _parent_impl,
attrs = { "my_field_provider": attr.label() }
)
# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")
dep_rule(name = "dep")
parent_rule(name = "parent", my_field_provider = ":my_field_provider")
label_flag(
name = "my_field_provider",
build_setting_default = ":dep"
)
Configurações de build e select()
Os usuários podem definir atributos nas configurações de build usando
select()
Os destinos da configuração do build podem ser transmitidos para o atributo flag_values
do
config_setting
. O valor para corresponder à configuração é passado como um
Em seguida, o String
analisou o tipo de configuração do build para correspondência.
config_setting(
name = "my_config",
flag_values = {
"//example:favorite_flavor": "MANGO"
}
)
Transições definidas pelo usuário
Uma configuração transição mapeia a transformação de um destino configurado para outro dentro do gráfico de build.
As regras que as definem precisam incluir um atributo especial:
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
)
Ao adicionar transições, você pode facilmente explodir o tamanho de seu gráfico de compilação. Isso define uma lista de permissões nos pacotes em que você pode criar segmentações desta regra. O valor padrão no bloco de código acima coloca tudo na lista de permissões. Mas, se quiser restringir quem usa a regra, você pode configurar esse atributo para apontar para sua própria lista de permissões personalizada. Entre em contato com bazel-discuss@googlegroups.com se precisar de orientação ou assistência entender como as transições podem afetar o desempenho do seu build.
Definição
As transições definem mudanças de configuração entre as regras. Por exemplo, uma solicitação como "compilar minha dependência para uma CPU diferente da mãe" é processado por um a transição.
Oficialmente, uma transição é uma função de uma configuração de entrada para um ou mais
configurações de saída. A maioria das transições é 1:1, como "substituir a entrada
com --cpu=ppc
". Transições individuais também podem existir, mas
com restrições especiais.
No Starlark, as transições são definidas como regras, com uma
transition()
função
e uma função de implementação.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//example:favorite_flavor" : "MINT"}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
A função transition()
usa uma função de implementação, um conjunto de
configurações de build para read(inputs
) e um conjunto de configurações de build para gravar
(outputs
). A função de implementação tem dois parâmetros, settings
e
attr
. settings
é um dicionário {String
:Object
} de todas as configurações declaradas.
no parâmetro inputs
para transition()
.
attr
é um dicionário de atributos e valores da regra à qual o
em anexo. Quando anexado como um
transição de borda de saída, os valores dessas
atributos são todos configurados na resolução post-select(). Quando anexado como
uma transição de borda de entrada, attr
não
incluir todos os atributos que usam um seletor para resolver o valor. Se um
a transição de borda de entrada em --foo
lê o atributo bar
e também
selecionar em --foo
para definir o atributo bar
, haverá uma chance para o
transição de borda de entrada para ler o valor errado de bar
na transição.
A função de implementação deve retornar um dicionário (ou lista de
dicionários, no caso
transições com várias configurações de saída)
novos valores de configurações de build para aplicar. Os conjuntos de chaves do dicionário retornados devem
contêm exatamente o conjunto de configurações de build transmitido para a outputs
da função de transição. Isso é válido mesmo se uma configuração de build for
realmente não mudaram durante a transição - seu valor original deve
ser explicitamente transmitido no dicionário retornado.
Como definir transições 1:2+
A transição de borda de saída pode mapear uma única entrada. a duas ou mais configurações de saída. Isso é útil para definir regras que agrupam códigos de multiarquitetura.
As transições individuais são definidas ao retornar uma lista de dicionários no função de implementação de transição.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return [
{"//example:favorite_flavor" : "LATTE"},
{"//example:favorite_flavor" : "MOCHA"},
]
coffee_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
Eles também podem definir chaves personalizadas que a função de implementação de regras pode usar para ler dependências individuais:
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
Como anexar transições
As transições podem ser anexadas em dois lugares: bordas de entrada e bordas de saída. Isso significa que as regras podem fazer a transição da própria configuração (o transição de borda) e fazer a transição das dependências configurações (saída transição de borda).
OBSERVAÇÃO: atualmente, não é possível anexar transições do Starlark a regras nativas. Se precisar fazer isso, entre em contato com bazel-discuss@googlegroups.com para encontrar soluções alternativas.
Transições de borda de entrada
As transições de borda recebidas são ativadas ao anexar um objeto transition
(criado por transition()
) para o parâmetro cfg
de rule()
:
# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
implementation = _impl,
cfg = hot_chocolate_transition,
...
As transições de borda recebidas precisam ser de 1:1.
Transições de borda enviadas
As transições de borda de saída são ativadas ao anexar um objeto transition
(criado por transition()
) ao parâmetro cfg
de um atributo:
# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
implementation = _impl,
attrs = { "dep": attr.label(cfg = coffee_transition)}
...
As transições de borda podem ser de 1:1 ou 1:2+.
Consulte Como acessar atributos com transições para saber como ler essas chaves.
Transições em opções nativas
As transições Starlark também podem declarar leituras e gravações no build nativo por um prefixo especial ao nome da opção.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//command_line_option:cpu": "k8"}
cpu_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
Opções nativas incompatíveis
O Bazel não oferece suporte à transição em --define
com
"//command_line_option:define"
. Em vez disso, use um
do build. Em geral, os novos usos
Recomendamos usar --define
em favor das configurações de build.
O Bazel não oferece suporte à transição no --config
. Isso ocorre porque --config
está
uma "expansão" que se expande para outras sinalizações.
É crucial que o --config
inclua flags que não afetam a configuração do build.
como
--spawn_strategy
, Por padrão, o Bazel não pode vincular essas flags a destinos individuais. Isso significa que
não há uma maneira coerente de aplicá-los em transições.
Como solução alternativa, você pode discriminar explicitamente as sinalizações que fazem parte
a configuração em sua transição. Isso exige manter a classe --config
expansão em dois lugares, que é uma falha conhecida da interface.
As transições permitem várias configurações de build
Ao definir configurações de build que permitir vários valores, o valor do atributo precisa ser definida com uma lista.
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
# Using a value of just "dark" here will throw an error
return {"//example:roasts" : ["dark"]},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:roasts"]
)
Transições de ambiente autônomo
Se uma transição retornar {}
, []
ou None
, ela será uma abreviação de manter todos
com os valores originais. Isso pode ser mais conveniente do que explicitamente
definindo cada saída para si mesma.
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (attr)
if settings["//example:already_chosen"] is True:
return {}
return {
"//example:favorite_flavor": "dark chocolate",
"//example:include_marshmallows": "yes",
"//example:desired_temperature": "38C",
}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = ["//example:already_chosen"],
outputs = [
"//example:favorite_flavor",
"//example:include_marshmallows",
"//example:desired_temperature",
]
)
Como acessar atributos com transições
Ao anexar uma transição a uma borda de saída
(independentemente de a transição ser uma transição de 1:1 ou 1:2+), ctx.attr
é forçado a ser uma lista.
caso ainda não tenha feito isso. A ordem dos elementos na lista não foi especificada.
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
return {"//example:favorite_flavor" : "LATTE"},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
def _rule_impl(ctx):
# Note: List access even though "dep" is not declared as list
transitioned_dep = ctx.attr.dep[0]
# Note: Access doesn't change, other_deps was already a list
for other dep in ctx.attr.other_deps:
# ...
coffee_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = coffee_transition)
"other_deps": attr.label_list(cfg = coffee_transition)
})
Se a transição for 1:2+
e definir chaves personalizadas, ctx.split_attr
poderá ser usado
para ler dependências individuais de cada chave:
# example/transitions/rules.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
def _rule_impl(ctx):
apple_dep = ctx.split_attr.dep["Apple deps"]
linux_dep = ctx.split_attr.dep["Linux deps"]
# ctx.attr has a list of all deps for all keys. Order is not guaranteed.
all_deps = ctx.attr.dep
multi_arch_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = multi_arch_transition)
})
Veja um exemplo completo aqui.
Integração com plataformas e conjuntos de ferramentas
Muitas sinalizações nativas atuais, como --cpu
e --crosstool_top
, estão relacionadas a
resolução do conjunto de ferramentas. No futuro, transições explícitas nesses tipos de
as sinalizações serão substituídas pela transição
plataforma de destino.
Considerações sobre memória e desempenho
Adicionar transições e, portanto, novas configurações, ao seu build tem um custo: gráficos de criação maiores, gráficos de criação menos compreensíveis e gráficos mais lentos builds. É importante considerar esses custos usando transições nas regras de build. Veja abaixo um exemplo de como uma transição pode gerar um crescimento exponencial do seu gráfico de criação.
Builds com mau comportamento: um estudo de caso
Figura 1. Gráfico de escalonabilidade mostrando um destino de nível superior e as dependências dele.
Este gráfico mostra um destino de nível superior, //pkg:app, que depende de dois destinos, um //pkg:1_0 e //pkg:1_1. Os dois destinos dependem de dois destinos, //pkg:2_0 e //pkg:2_1 Os dois destinos dependem de dois destinos, //pkg:3_0 e //pkg:3_1. Isso continua até //pkg:n_0 e //pkg:n_1, que dependem de um único target, //pkg:dep.
A criação de //pkg:app
requer \(2n+2\) destinos:
//pkg:app
//pkg:dep
//pkg:i_0
e//pkg:i_1
para \(i\) em \([1..n]\)
Imagine que você implementa) uma sinalização
--//foo:owner=<STRING>
e //pkg:i_b
se aplicam
depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"
Em outras palavras, //pkg:i_b
anexa b
ao valor antigo de --owner
para todos
dependências.
Isso produz os seguintes destinos configurados:
//pkg:app //foo:owner=""
//pkg:1_0 //foo:owner=""
//pkg:1_1 //foo:owner=""
//pkg:2_0 (via //pkg:1_0) //foo:owner="0"
//pkg:2_0 (via //pkg:1_1) //foo:owner="1"
//pkg:2_1 (via //pkg:1_0) //foo:owner="0"
//pkg:2_1 (via //pkg:1_1) //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0) //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1) //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0) //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1) //foo:owner="11"
...
//pkg:dep
produz \(2^n\) destinos configurados: config.owner=
"\(b_0b_1...b_n\)" para todos \(b_i\) em \(\{0,1\}\).
Isso torna o gráfico de criação exponencialmente maior que o gráfico de destino, com consequências correspondentes de memória e desempenho.
O que fazer: adicione estratégias para medição e mitigação desses problemas.
Leitura adicional
Para saber mais sobre como modificar configurações de build, consulte:
- Configuração do build do Starlark
- Roteiro de configuração do Bazel
- Conjunto completo de exemplos completos