Atributos de build configuráveis

Informar um problema Acessar a origem

Os atributos configuráveis, geralmente conhecidos como select(), são um recurso do Bazel que permite aos usuários alternar os valores dos atributos da regra de build na linha de comando.

Isso pode ser usado, por exemplo, para uma biblioteca multiplataforma que escolhe automaticamente a implementação adequada para a arquitetura ou para um binário configurável por recursos que pode ser personalizado no tempo de build.

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 próprias 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 posição para um valor que é escolhido com base em condições de configuração, que são rótulos que fazem referência a destinos config_setting. Usando select() em um atributo configurável, o atributo implementa efetivamente valores diferentes quando condições distintas são válidas.

As correspondências não podem ser ambíguas: se várias condições forem correspondentes, todas elas terão o mesmo valor. Por exemplo, ao executar no Linux x86, o {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} não é ambíguo porque as duas ramificações são resolvidas como "hello". * O values de um é um superconjunto estrito de todos os 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 tem correspondência automática quando não há mais nada a fazer.

Embora este exemplo use deps, select() também funciona bem em srcs, resources, cmd e na maioria dos outros atributos. Apenas um pequeno número de atributos é não configurável e está anotado com clareza. Por exemplo, o próprio atributo values da config_setting não é configurável.

select() e dependências

Alguns atributos alteram os parâmetros de build 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 que, graças à compilação cruzada, pode ser diferente da CPU para que o destino foi criado. Isso é conhecido como transição de configuração.

Recebido

#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. Ambas as selects anexadas a my_genrule usam os parâmetros de build do my_genrule, que incluem --cpu=arm. O atributo tools muda --cpu para x86 para tool1 e as dependências transitivas dele. O select em 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 a config_setting ou constraint_value.

config_setting é apenas um conjunto de configurações de sinalização de linha de comando esperadas. Ao encapsular esses elementos em um destino, é fácil manter as condições "padrão" que os usuários podem referenciar de vários lugares.

constraint_value é compatível com o comportamento multiplataforma.

Sinalizações integradas

Sinalizações como --cpu são integradas ao Bazel: a ferramenta de build as entende nativamente de 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 é o nome de uma sinalização (sem --, portanto, "cpu" em vez de "--cpu"). valueN é o valor esperado para essa sinalização. :meaningful_condition_name corresponde se todas as entradas em values forem correspondentes. O pedido é irrelevante.

valueN é analisado como se tivesse sido definido na linha de comando. Isso significa que:

  • values = { "compilation_mode": "opt" } corresponde bazel build -c opt
  • values = { "force_pic": "true" } corresponde bazel build --force_pic=1
  • values = { "force_pic": "0" } corresponde bazel build --noforce_pic

config_setting só oferece suporte a sinalizações que afetam o comportamento do destino. Por exemplo, --show_progress não é permitido porque afeta apenas o progresso dos relatórios do Bazel para o usuário. Os destinos não podem usar essa sinalização para criar resultados. O conjunto exato de sinalizações compatíveis não está documentado. Na prática, a maioria das sinalizações que "fazem sentido" funciona.

Sinalizações personalizadas

Você pode modelar suas próprias flags 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 build. Portanto, o Bazel as referencia com rótulos de destino.

Elas são acionadas com 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. Clique aqui para ver um exemplo funcional.

--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 define_values (define_values = {"foo": "bar"}). --define só tem suporte para compatibilidade com versões anteriores. Prefira as configurações de build do Starlark sempre que possível.

values, flag_values e define_values são avaliados de forma independente. O config_setting corresponde se todos os valores em todas elas forem correspondentes.

A condição padrão

A condição integrada //conditions:default faz a correspondência quando nenhuma outra condição corresponde.

Devido à regra "exatamente uma correspondência", um atributo configurável sem correspondência e sem condição padrão emite um erro "no matching conditions". Isso pode proteger contra falhas silenciosas de 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, você pode definir mensagens personalizadas com o atributo no_match_error da select().

Plataformas

Embora a capacidade de especificar várias sinalizações na linha de comando ofereça flexibilidade, também pode ser trabalhoso definir cada uma individualmente sempre que você quiser criar um destino. As plataformas permitem consolidá-las 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 os config_settings que contêm um subconjunto do constraint_values da plataforma, permitindo que esses config_settings correspondam em expressões select().

Por exemplo, para definir o atributo srcs de my_rocks como calcite.sh, você pode simplesmente executar

bazel build //my_app:my_rocks --platforms=//myapp:marble_platform

Sem plataformas, isso pode parecer algo como

bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic

select() também pode ler constraint_values 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_settings boilerplate quando você só precisa verificar valores únicos.

As plataformas ainda estão em desenvolvimento. Veja a documentação para 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 uma select para corresponder quando várias condições forem atendidas, considere o uso de E encadeamento.

Encadeamento OR

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 dependência, 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. Ainda assim, isso causa duplicações desnecessárias.

Para receber suporte mais direto, use uma destas opções:

selects.with_or

A macro with_or no módulo selects do Skylib (links em inglês) é compatível com condições ORing 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 ao uso de ORde várias config_settings:

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 diferentes.

A correspondência de várias condições é um erro, a menos que uma delas seja uma "especialização" não ambígua das outras ou todas resolvidas para o mesmo valor. Clique aqui para ver mais detalhes.

Encadeamento

Se você precisar que uma ramificação select corresponda quando várias condições forem atendidas, use config_setting_group a macro Skylib (link em inglês):

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_settings existentes não podem ser AND diretamente dentro de uma select. É necessário envolvê-las explicitamente em um config_setting_group.

Mensagens de erro personalizadas

Por padrão, quando nenhuma condição corresponde, o destino ao qual select() é anexado falha 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 com as regras

As implementações de regras recebem os valores resolvidos de atributos configuráveis. Por exemplo, considerando:

# 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 da regra vê ctx.attr.some_attr como [":foo"].

As macros podem aceitar cláusulas select() e transmiti-las para regras nativas. No entanto, eles não podem manipulá-los diretamente. Por exemplo, não há como uma macro converter

select({"foo": "val"}, ...)

a

select({"foo": "val_with_suffix"}, ...)

Isso se deve a dois motivos.

Primeiro, as macros que precisam saber qual caminho um select escolherá não podem funcionar porque são avaliadas na fase de carregamento do Bazel, que ocorre antes que os valores de sinalização sejam conhecidos. Essa é uma restrição de design principal do Bazel que provavelmente não vai mudar em breve.

Em segundo lugar, as macros que só precisam iterar em todos os caminhos select, embora tecnicamente viável, não tenham uma interface coerente. É preciso mais desenvolvimento para mudar isso.

Consulta e cquery do Bazel

O query do Bazel opera na fase de carregamento dele. Isso significa que ele não sabe quais sinalizações de linha de comando um destino usa, já que essas flags não são avaliadas até mais tarde na criação (na fase de análise). Por isso, ela não pode determinar quais ramificações select() são escolhidas.

O cquery (link em inglês) do Bazel opera após a fase de análise. 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 excede em excesso as dependências de :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 select() não funciona em macros?

select() funciona em regras. Confira mais detalhes em Compatibilidade com as regras.

O principal problema que essa pergunta geralmente significa é que select() não funciona em macros. Eles 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 o 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 construção funciona 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.

Isso é impossível de alterar porque as macros por definição são avaliadas antes de o Bazel ler 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 as 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 true?

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 falham silenciosamente. Por isso, tome cuidado com 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(). Eles estão avaliando o próprio objeto select(). De acordo com os padrões de design Python, todos os objetos, exceto um número muito pequeno de exceções, retornam automaticamente verdadeiros.

Posso ler select() como um dict?

As macros não podem avaliar seleção(s) porque elas são avaliadas antes de o Bazel saber 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 recurso do Bazel. O que você pode fazer hoje é preparar um dicionário direto e adicioná-lo a 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 suporte aos tipos select() e nativos, 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 select() não funciona com bind()?

Primeiro, não use bind(). Ele foi descontinuado e substituído por alias().

A resposta técnica é que bind() é uma regra de repositório, não uma regra BUILD.

As regras do repositório não têm uma configuração específica e não são avaliadas da mesma forma que as regras BUILD. Portanto, um select() em um bind() não pode ser avaliado como uma ramificação específica.

Em vez disso, use alias(), com um select() no atributo actual, para realizar esse tipo de determinação do tempo de execução. Isso funciona corretamente, já que alias() é uma regra BUILD e é avaliada com uma configuração específica.

$ 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.

Mas, na verdade, pare de usar bind().

Por que meu select() não escolhe o que eu espero?

Se //myapp:foo tiver um select() que não escolha 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 algum outro //bar de destino 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 o 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 por cada config_setting.

//myapp:foo pode existir em configurações diferentes no mesmo build. Consulte os documentos do cquery (em inglês) para ver orientações sobre como usar o somepath e criar o correto.

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 está 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() vai ser usada 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 semânticas específicas, deixando claro para os usuários quais plataformas atendem às condições desejadas.

E se eu quiser muito select na plataforma?

Se os requisitos de build exigirem especificamente a verificação da plataforma, você poderá inverter 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 endossa essa ação. Ela restringe excessivamente sua versão e confunde os usuários quando a condição esperada não corresponde.