Os atributos configuráveis, conhecidos como select()
, são um recurso do Bazel que permite aos usuários alternar os valores
de atributos de regra de compilação na linha de comando.
Isso pode ser usado, por exemplo, para uma biblioteca multiplataforma que escolhe automaticamente a implementação apropriada para a arquitetura ou para um binário configurável pelo recurso que pode ser personalizado durante a compilação.
Exemplo
# myapp/BUILD
cc_binary(
name = "mybinary",
srcs = ["main.cc"],
deps = select({
":arm_build": [":arm_lib"],
":x86_debug_build": [":x86_dev_lib"],
"//conditions:default": [":generic_lib"],
}),
)
config_setting(
name = "arm_build",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_debug_build",
values = {
"cpu": "x86",
"compilation_mode": "dbg",
},
)
Isso declara um cc_binary
que "escolhe" as dependências com base nas sinalizações na
linha de comando. Especificamente, deps
se torna:
Comando | dependências = |
bazel build //myapp:mybinary --cpu=arm |
[":arm_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=x86 |
[":x86_dev_lib"] |
bazel build //myapp:mybinary --cpu=ppc |
[":generic_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=ppc |
[":generic_lib"] |
select()
serve como um marcador de valor para um valor escolhido com base nas
condições de configuração, que são rótulos que fazem referência a destinos
config_setting
. Ao usar select()
em um atributo configurável, o atributo
adota valores diferentes de maneira efetiva quando condições diferentes são mantidas.
As correspondências precisam ser não ambíguas: se várias condições corresponderem, todas* serão resolvidas com o mesmo valor. Por exemplo, ao executar no x86 do Linux, isso não é ambíguo
{"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}
porque as duas ramificações são resolvidas como "hello".
* O values
é um superconjunto rígido dos outros. Por exemplo, values = {"cpu": "x86", "compilation_mode": "dbg"}
é uma especialização não ambígua de values = {"cpu": "x86"}
.
A condição integrada //conditions:default
corresponde automaticamente quando
não há mais nada.
Embora esse exemplo use deps
, select()
também funciona em srcs
,
resources
, cmd
e na maioria dos outros atributos. Apenas um pequeno número de atributos
não é configurável, e estes são claramente anotados. Por exemplo, o próprio atributo values
de config_setting
não é configurável.
select()
e dependências
Alguns atributos mudam os parâmetros de compilação para todas as dependências transitivas
em um destino. Por exemplo, o tools
do genrule
muda --cpu
para a CPU da
máquina que executa o Bazel. Isso, graças à compilação cruzada, pode ser diferente
da CPU em que o destino for criado. Isso é conhecido como
transição de configuração.
Dado
#myapp/BUILD
config_setting(
name = "arm_cpu",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
genrule(
name = "my_genrule",
srcs = select({
":arm_cpu": ["g_arm.src"],
":x86_cpu": ["g_x86.src"],
}),
tools = select({
":arm_cpu": [":tool1"],
":x86_cpu": [":tool2"],
}),
)
cc_binary(
name = "tool1",
srcs = select({
":arm_cpu": ["armtool.cc"],
":x86_cpu": ["x86tool.cc"],
}),
)
em execução
$ bazel build //myapp:my_genrule --cpu=arm
em uma máquina de desenvolvimento x86
vincula o build a g_arm.src
, tool1
e x86tool.cc
. As select
s anexadas a my_genrule
usam os parâmetros de build de
my_genrule
, que incluem --cpu=arm
. O atributo tools
muda
--cpu
para x86
para tool1
e as dependências transitivas dele. O select
na
tool1
usa os parâmetros de build do tool1
, que incluem --cpu=x86
.
Condições de configuração
Cada chave em um atributo configurável é uma referência de rótulo para uma
config_setting
ou
constraint_value
.
config_setting
é apenas um conjunto de
configurações esperadas de sinalização de linha de comando. Encapsulando-as em um destino, é
fácil manter condições "padrão" que os usuários podem referenciar de vários lugares.
O constraint_value
oferece suporte ao comportamento de várias plataformas.
Sinalizações integradas
Sinalizações como --cpu
são integradas ao Bazel: a ferramenta de compilação as entende nativamente
para todos os builds em todos os projetos. Elas são especificadas com o atributo
values
de
config_setting
:
config_setting(
name = "meaningful_condition_name",
values = {
"flag1": "value1",
"flag2": "value2",
...
},
)
flagN
é um nome de sinalização (sem --
, portanto, "cpu"
em vez de "--cpu"
). valueN
é o valor esperado para essa sinalização. :meaningful_condition_name
corresponde a
todas as entradas em values
. A ordem é irrelevante.
valueN
é analisado como se fosse definido na linha de comando. Isso significa que:
values = { "compilation_mode": "opt" }
correspondebazel build -c opt
values = { "force_pic": "true" }
correspondebazel build --force_pic=1
values = { "force_pic": "0" }
correspondebazel build --noforce_pic
config_setting
só oferece suporte a sinalizações que afetam o comportamento do destino. Por exemplo, a propriedade --show_progress
não é permitida porque afeta apenas a forma como o Bazel informa o progresso para o usuário. Os destinos não podem usar essa
sinalização para construir os resultados. O conjunto exato de sinalizações compatíveis não está documentado. Na prática, a maioria das sinalizações que fazem sentido funcionam.
Sinalizações personalizadas
Você pode modelar suas próprias sinalizações específicas do projeto com as configurações de build do Starlark. Ao contrário das sinalizações integradas, elas são definidas como destinos de compilação. Portanto, o Bazel as referencia com rótulos de destino.
Elas são acionadas usando o atributo
flag_values
de config_setting
:
config_setting(
name = "meaningful_condition_name",
flag_values = {
"//myflags:flag1": "value1",
"//myflags:flag2": "value2",
...
},
)
O comportamento é o mesmo das sinalizações integradas. Veja um exemplo prático.
--define
é uma sintaxe legada alternativa para sinalizações personalizadas (por exemplo,
--define foo=bar
). Isso pode ser expresso no atributo
values
(values = {"define": "foo=bar"}
) ou no atributo
define_values (define_values = {"foo": "bar"}
). --define
só é compatível com a compatibilidade
com versões anteriores. Prefira as configurações de compilação do Starlark sempre que possível.
values
, flag_values
e define_values
são avaliados de forma independente. O
config_setting
vai corresponder se todos os valores em todos eles corresponderem.
Condição padrão
A condição integrada //conditions:default
é correspondente quando nenhuma outra condição
corresponde.
Devido à regra "exatamente uma correspondência", um atributo configurável sem correspondência
e nenhuma condição padrão emite um erro "no matching conditions"
. Isso pode
proteger contra falhas silenciosas contra configurações inesperadas:
# myapp/BUILD
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
cc_library(
name = "x86_only_lib",
srcs = select({
":x86_cpu": ["lib.cc"],
}),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//myapp:x86_cpu
Para erros ainda mais claros, defina mensagens personalizadas com o atributo
no_match_error
de select()
.
Plataformas
A capacidade de especificar várias sinalizações na linha de comando oferece flexibilidade, mas também pode ser trabalhoso definir cada uma delas sempre que você quiser criar um destino. As plataformas permitem consolidar esses pacotes em pacotes simples.
# myapp/BUILD
sh_binary(
name = "my_rocks",
srcs = select({
":basalt": ["pyroxene.sh"],
":marble": ["calcite.sh"],
"//conditions:default": ["feldspar.sh"],
}),
)
config_setting(
name = "basalt",
constraint_values = [
":black",
":igneous",
],
)
config_setting(
name = "marble",
constraint_values = [
":white",
":metamorphic",
],
)
# constraint_setting acts as an enum type, and constraint_value as an enum value.
constraint_setting(name = "color")
constraint_value(name = "black", constraint_setting = "color")
constraint_value(name = "white", constraint_setting = "color")
constraint_setting(name = "texture")
constraint_value(name = "smooth", constraint_setting = "texture")
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
platform(
name = "basalt_platform",
constraint_values = [
":black",
":igneous",
],
)
platform(
name = "marble_platform",
constraint_values = [
":white",
":smooth",
":metamorphic",
],
)
A plataforma pode ser especificada na linha de comando. Ele ativa as
config_setting
s que contêm um subconjunto do constraint_values
da plataforma,
permitindo que essas config_setting
s correspondam às expressões select()
.
Por exemplo, para definir o atributo srcs
de my_rocks
como calcite.sh
,
basta executar
bazel build //my_app:my_rocks --platforms=//myapp:marble_platform
Sem as plataformas, isso pode ficar parecido com
bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic
select()
também pode ler constraint_value
s diretamente:
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
name = "my_rocks",
srcs = select({
":igneous": ["igneous.sh"],
":metamorphic" ["metamorphic.sh"],
}),
)
Isso evita a necessidade de config_setting
s padrão quando você precisa apenas verificar valores únicos.
As plataformas ainda estão em desenvolvimento. Consulte a documentação para ver mais detalhes.
Combinando select()
s
select
pode aparecer várias vezes no mesmo atributo:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"] +
select({
":armeabi_mode": ["armeabi_src.sh"],
":x86_mode": ["x86_src.sh"],
}) +
select({
":opt_mode": ["opt_extras.sh"],
":dbg_mode": ["dbg_extras.sh"],
}),
)
select
não pode aparecer dentro de outro select
. Se você precisar aninhar selects
e seu atributo usar outros destinos como valores, use um destino intermediário:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":armeabi_mode": [":armeabi_lib"],
...
}),
)
sh_library(
name = "armeabi_lib",
srcs = select({
":opt_mode": ["armeabi_with_opt.sh"],
...
}),
)
Se você precisar de um select
para corresponder quando várias condições corresponderem, considere usar E
encadear.
OU encadeamento
Recomendamos o seguinte:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": [":standard_lib"],
":config2": [":standard_lib"],
":config3": [":standard_lib"],
":config4": [":special_lib"],
}),
)
A maioria das condições é avaliada para a mesma dep. Mas essa sintaxe é difícil de ler e
manter. Seria bom não ter que repetir [":standard_lib"]
várias
vezes.
Uma opção é predefinir o valor como uma variável BUILD:
STANDARD_DEP = [":standard_lib"]
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": STANDARD_DEP,
":config2": STANDARD_DEP,
":config3": STANDARD_DEP,
":config4": [":special_lib"],
}),
)
Isso facilita o gerenciamento da dependência. Mas isso ainda causa duplicação desnecessária.
Para receber suporte direto, use uma destas opções:
selects.with_or
A macro
with_or
no módulo
selects
do
Skylib oferece suporte a condições OR
diretamente dentro de um select
:
load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = selects.with_or({
(":config1", ":config2", ":config3"): [":standard_lib"],
":config4": [":special_lib"],
}),
)
selects.config_setting_group
A macro
config_setting_group
no módulo selects
do Skylib
oferece suporte a várias config_setting
s:
load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_or_2",
match_any = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_or_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
Ao contrário de selects.with_or
, destinos diferentes podem compartilhar :config1_or_2
em
atributos distintos.
É um erro que várias condições sejam correspondentes, a menos que uma seja uma "especialização" não ambígua das outras ou que todas resolvam para o mesmo valor. Veja mais detalhes aqui.
E encadeamento
Se você precisar de uma ramificação select
para corresponder a várias condições, use a macro Skylib config_setting_group:
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_and_2",
match_all = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_and_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
Ao contrário do encadeamento OR, os config_setting
s não podem ser AND
diretamente
dentro de um select
. Eles precisam ser unidos explicitamente em um config_setting_group
.
Mensagens de erro personalizadas
Por padrão, quando nenhuma condição corresponder, o destino ao qual o select()
está anexado
falhará com o erro:
ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//tools/cc_target_os:darwin
//tools/cc_target_os:android
Isso pode ser personalizado com o atributo
no_match_error
:
cc_library(
name = "my_lib",
deps = select(
{
"//tools/cc_target_os:android": [":android_deps"],
"//tools/cc_target_os:windows": [":windows_deps"],
},
no_match_error = "Please build with an Android or Windows toolchain",
),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain
Compatibilidade de regras
As implementações de regras recebem os valores resolvidos dos atributos configuráveis. Exemplo:
# myapp/BUILD
some_rule(
name = "my_target",
some_attr = select({
":foo_mode": [":foo"],
":bar_mode": [":bar"],
}),
)
$ bazel build //myapp/my_target --define mode=foo
O código de implementação de regra vê ctx.attr.some_attr
como [":foo"]
.
As macros podem aceitar cláusulas select()
e transmiti-las para regras
nativas. Mas eles não podem manipulá-los diretamente. Por exemplo, não é possível
converter uma macro
select({"foo": "val"}, ...)
to
select({"foo": "val_with_suffix"}, ...)
Isso ocorreu por dois motivos.
Primeiro, as macros que precisam saber qual caminho um select
escolherá não podem funcionar porque as macros são avaliadas na fase de carregamento do Bazel, que ocorre antes dos valores da sinalização serem conhecidos.
Essa é uma restrição básica de design do Bazel que provavelmente não será alterada em breve.
Em segundo lugar, as macros que só precisam fazer iterações de todos os caminhos select
, embora
tecnicamente viáveis, não têm uma IU coerente. É necessário mais design para mudar
isso.
Consulta e cquery do Bazel
O Bazel query
opera na fase de carregamento do Bazel.
Isso significa que ele não sabe quais sinalizações de linha de comando um destino usa, porque essas sinalizações não são avaliadas até que o build esteja mais tarde (na fase de análise).
Portanto, não é possível determinar quais ramificações select()
são escolhidas.
O Bazel cquery
opera após a fase de análise do Bazel. Portanto, ele tem todas essas informações e pode resolver select()
s com precisão.
Considere:
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD
string_flag(
name = "dog_type",
build_setting_default = "cat"
)
cc_library(
name = "my_lib",
deps = select({
":long": [":foo_dep"],
":short": [":bar_dep"],
}),
)
config_setting(
name = "long",
flag_values = {":dog_type": "dachshund"},
)
config_setting(
name = "short",
flag_values = {":dog_type": "pug"},
)
query
supera as dependências do :my_lib
:
$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep
Enquanto cquery
mostra as dependências exatas:
$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep
Perguntas frequentes
Por que o método select() não funciona em macros?
select() funciona em regras. Consulte os detalhes em Compatibilidade com regras.
O problema principal que essa pergunta geralmente significa é que select() não funciona em macros. Elas são diferentes das regras. Consulte a documentação sobre regras e macros para entender a diferença. Veja um exemplo completo:
Defina uma regra e uma macro:
# myapp/defs.bzl
# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
name = ctx.attr.name
allcaps = ctx.attr.my_config_string.upper() # This works fine on all values.
print("My name is " + name + " with custom message: " + allcaps)
# Rule declaration:
my_custom_bazel_rule = rule(
implementation = _impl,
attrs = {"my_config_string": attr.string()},
)
# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
allcaps = my_config_string.upper() # This line won't work with select(s).
print("My name is " + name + " with custom message: " + allcaps)
Instancie a regra e a macro:
# myapp/BUILD
load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")
my_custom_bazel_rule(
name = "happy_rule",
my_config_string = select({
"//tools/target_cpu:x86": "first string",
"//third_party/bazel_platforms/cpu:ppc": "second string",
}),
)
my_custom_bazel_macro(
name = "happy_macro",
my_config_string = "fixed string",
)
my_custom_bazel_macro(
name = "sad_macro",
my_config_string = select({
"//tools/target_cpu:x86": "first string",
"//third_party/bazel_platforms/cpu:ppc": "other string",
}),
)
A criação falha porque sad_macro
não pode processar o select()
:
$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.
A criação é bem-sucedida quando você comenta sad_macro
:
# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.
É impossível alterar isso porque as macros por definição são avaliadas antes que o Bazel leia as sinalizações de linha de comando do build. Isso significa que não há informações suficientes para avaliar select()s.
No entanto, as macros podem transmitir select()
s como blobs opacos para regras:
# myapp/defs.bzl
def my_custom_bazel_macro(name, my_config_string):
print("Invoking macro " + name)
my_custom_bazel_rule(
name = name + "_as_target",
my_config_string = my_config_string,
)
$ bazel build //myapp:sad_macro_less_sad
DEBUG: /myworkspace/myapp/defs.bzl:23:3: Invoking macro sad_macro_less_sad.
DEBUG: /myworkspace/myapp/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING.
Por que select() sempre retorna verdadeiro?
Como as macros (mas não as regras) por definição não podem avaliar select()
s, qualquer tentativa de fazer isso geralmente produz um erro:
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
Os booleanos são um caso especial que falha silenciosamente. Por isso, fique especialmente atento a eles:
$ cat myapp/defs.bzl
def my_boolean_macro(boolval):
print("TRUE" if boolval else "FALSE")
$ cat myapp/BUILD
load("//myapp:defs.bzl", "my_boolean_macro")
my_boolean_macro(
boolval = select({
"//tools/target_cpu:x86": True,
"//third_party/bazel_platforms/cpu:ppc": False,
}),
)
$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
Isso acontece porque as macros não entendem o conteúdo de select()
.
O que eles estão realmente avaliando é o próprio objeto select()
. De acordo com os padrões de design
Python,
todos os objetos, exceto um pequeno número de exceções,
retornam automaticamente "true".
Posso ler select() como um dict?
As macros não podem avaliar as seleções porque elas são avaliadas antes que o Bazel saiba quais são os parâmetros da linha de comando do build. Eles podem pelo menos ler
o dicionário de select()
para, por exemplo, adicionar um sufixo a cada valor?
Conceitualmente, isso é possível, mas ainda não é um atributo do Bazel.
O que você pode fazer é preparar um dicionário reto e depois alimentar um
select()
:
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
+ " > $@"
)
$ cat myapp/BUILD
selecty_genrule(
name = "selecty",
select_cmd = {
"//tools/target_cpu:x86": "x86 mode",
},
)
$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX
Se você quiser oferecer compatibilidade com tipos nativos e select()
, faça o seguinte:
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
cmd_suffix = ""
if type(select_cmd) == "string":
cmd_suffix = select_cmd + " WITH SUFFIX"
elif type(select_cmd) == "dict":
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
cmd_suffix = select(select_cmd + {"//conditions:default": "default"})
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + cmd_suffix + "> $@",
)
Por que o método select() não funciona com o bind()?
Porque bind()
é uma regra WORKSPACE, não uma regra BUILD.
As regras do espaço de trabalho não têm uma configuração específica e não são avaliadas da
mesma forma que as regras da BUILD. Portanto, uma select()
em um bind()
não pode
realmente avaliar para qualquer ramificação específica.
Em vez disso, use alias()
, com um select()
no
atributo actual
, para realizar esse tipo de determinação do ambiente de execução. Isso
funciona corretamente, já que alias()
é uma regra BUILD e é avaliado com uma
configuração específica.
É possível até mesmo ter um ponto de destino bind()
para um alias()
, se necessário.
$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)
$ cat BUILD
config_setting(
name = "alt_ssl",
define_values = {
"ssl_library": "alternative",
},
)
alias(
name = "ssl",
actual = select({
"//:alt_ssl": "@alternative//:ssl",
"//conditions:default": "@boringssl//:ssl",
}),
)
Com essa configuração, é possível transmitir --define ssl_library=alternative
, e qualquer destino
que dependa de //:ssl
ou //external:ssl
verá a alternativa
localizada em @alternative//:ssl
.
Por que meu select() não escolhe o que eu espero?
Se //myapp:foo
tiver um select()
que não escolhe a condição esperada,
use cquery e bazel config
para depurar:
Se //myapp:foo
for o destino de nível superior que você está criando, execute:
$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)
Se você estiver criando outro destino //bar
que dependa de //myapp:foo em algum lugar no subgráfico, execute:
$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)
O (12e23b9a2b534a)
ao lado de //myapp:foo
é um hash da
configuração que resolve select()
do //myapp:foo
. É possível inspecionar os
valores com bazel config
:
$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
cpu: darwin
compilation_mode: fastbuild
...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
linkopt: [-Dfoo=bar]
...
}
...
Em seguida, compare essa saída com as configurações esperadas para cada config_setting
.
//myapp:foo
pode existir em configurações diferentes no mesmo build. Consulte os
documentos de consulta para ver orientações sobre o uso do somepath
para receber o
documento certo.
Por que o select()
não funciona com plataformas?
O Bazel não oferece suporte a atributos configuráveis que verificam se uma determinada plataforma é a plataforma de destino porque a semântica não é clara.
Exemplo:
platform(
name = "x86_linux_platform",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
Neste arquivo BUILD
, qual select()
será usado se a plataforma de destino tiver as restrições
@platforms//cpu:x86
e @platforms//os:linux
, mas não for a
:x86_linux_platform
definida aqui? O autor do arquivo BUILD
e o usuário
que definiu a plataforma separada podem ter ideias diferentes.
O que devo fazer?
Em vez disso, defina um config_setting
que corresponda a qualquer plataforma com
estas restrições:
config_setting(
name = "is_x86_linux",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_x86_linux": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
Esse processo define uma semântica específica, deixando mais claro para os usuários quais plataformas atendem às condições desejadas.
E se eu quiser realmente select
na plataforma?
Se os requisitos de versão exigirem especificamente a verificação da plataforma, você pode virar o valor da sinalização --platforms
em um config_setting
:
config_setting(
name = "is_specific_x86_linux_platform",
values = {
"platforms": ["//package:x86_linux_platform"],
},
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
A equipe do Bazel não recomenda fazer isso. Ela restringe demais seu build e confunde os usuários quando a condição esperada não é adequada.