Atributos de build configuráveis

Relatar um problema Conferir código-fonte Por noite · 7,4 do Google. 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

Isso pode ser usado, por exemplo, para uma biblioteca multiplataforma que automaticamente escolhe a implementação adequada para a arquitetura ou para uma Binário configurável por recursos que pode ser personalizado no momento da 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 flags na linha de comando. Especificamente, deps se torna:

Comando deps =
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 será 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 efetivamente valores diferentes quando condições diferentes são mantidas.

As correspondências precisam ser inequívocas: se várias condições corresponderem, * todas serão resolvidas para o mesmo valor. Por exemplo, ao ser executado no Linux x86, isso é inequívoco para {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}, porque ambas as ramificações são resolvidas como "hello". * O values de um é um superconjunto rigoroso de todos os outros. Por exemplo, values = {"cpu": "x86", "compilation_mode": "dbg"} é uma especialização inequívoca de values = {"cpu": "x86"}.

A condição integrada //conditions:default corresponde automaticamente quando nada mais faz.

Embora este exemplo use deps, select() funciona da mesma forma em srcs, resources, cmd e na maioria dos outros atributos. Apenas um pequeno número de atributos são não configuráveis, e eles são claramente anotados. Por exemplo, o atributo values do config_setting não é configurável.

select() e dependências

Alguns atributos mudam 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 a qual o destino é 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 desenvolvedor x86 vincula o build a g_arm.src, tool1 e x86tool.cc. As duas selects anexadas a my_genrule usam a my_genrule parâmetros de build, que incluem --cpu=arm. O atributo tools muda --cpu para x86 em tool1 e suas dependências transitivas. O select em tool1 usa os parâmetros de build de tool1, que incluem --cpu=x86.

Condições de configuração

Cada chave em um atributo configurável é uma referência de rótulo a um config_setting ou constraint_value

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

O constraint_value oferece suporte ao comportamento multiplataforma.

Sinalizações integradas

Sinalizações como --cpu são integradas ao Bazel: a ferramenta de build entende nativamente para todos os builds em todos os projetos. Eles são especificados com De config_setting Atributo values:

config_setting(
    name = "meaningful_condition_name",
    values = {
        "flag1": "value1",
        "flag2": "value2",
        ...
    },
)

flagN é o nome de uma flag (sem --, ou seja, "cpu" em vez de "--cpu"). valueN é o valor esperado para essa flag. :meaningful_condition_name corresponde se cada entrada em values corresponder. A ordem é irrelevante.

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

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

config_setting só oferece suporte a flags que afetam o comportamento de destino. Por exemplo: --show_progress não é permitido porque ela afeta apenas a forma como o Bazel informa o progresso ao usuário. Os destinos não podem usar isso para criar os resultados. O conjunto exato de sinalizações com suporte não é documentadas. Na prática, a maioria das sinalizações que "faz sentido" funcionam.

Flags personalizadas

É possível modelar suas próprias sinalizações específicas do projeto com Configurações de build do Starlark. Ao contrário das sinalizações incorporadas, elas são definidos como destinos de compilação. Por isso, o Bazel faz referência a eles com rótulos de destino.

Eles são acionados com o config_setting flag_values :

config_setting(
    name = "meaningful_condition_name",
    flag_values = {
        "//myflags:flag1": "value1",
        "//myflags:flag2": "value2",
        ...
    },
)

O comportamento é o mesmo das flags integradas. Confira aqui um exemplo funcional.

--define é uma sintaxe legada alternativa para flags personalizadas (por exemplo, --define foo=bar). Isso pode ser expresso no atributo values (values = {"define": "foo=bar"}) ou no define_values (define_values = {"foo": "bar"}). --define tem suporte apenas para compatibilidade anterior. 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 corresponderem.

A condição padrão

A condição integrada //conditions:default é atendida quando nenhuma outra condição é atendida.

Por causa da "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 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 select() no_match_error.

Plataformas

Embora a capacidade de especificar várias sinalizações na linha de comando forneça ter flexibilidade, também pode ser trabalhoso definir cada um individualmente você quer criar um destino. Plataformas permitem consolidá-los 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 da constraint_values da plataforma, permitindo que esses config_settings sejam correspondentes em 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 plataformas, isso pode ficar assim:

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 elimina a necessidade de config_settings boilerplate quando você só precisa verificar valores únicos.

As plataformas ainda estão em desenvolvimento. Consulte a documentação para mais detalhes.

Combinar 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 corresponderem, considere AND encadeamento.

Encadeamento OR

Considere 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 avalia a mesma dependência. Mas essa sintaxe é difícil de ler manter. Seria bom não precisar 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 ainda faz com que seja desnecessário duplicação.

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

selects.with_or

O with_or no Skylib selects oferece suporte a condições OR diretamente em 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

O config_setting_group no Skylib selects oferece suporte a ORvários 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 entre atributos diferentes.

É um erro quando várias condições correspondem, a menos que uma delas seja inequívoca "especialização" dos outros ou todos eles têm o mesmo valor. Clique aqui para mais detalhes.

Encadeamento AND

Se você precisar que uma ramificação select corresponda 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_settings existentes não podem ser diretamente AND em um select. É necessário envolvê-los explicitamente em um config_setting_group.

Mensagens de erro personalizadas

Por padrão, quando nenhuma condição corresponde, o destino a que 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 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 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 de regras vê ctx.attr.some_attr como [":foo"].

As macros podem aceitar cláusulas select() e transmiti-las para o ambiente nativo regras de firewall. 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 acontece por dois motivos.

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

Em segundo lugar, as macros que precisam iterar sobre todos os caminhos select, embora tecnicamente viáveis, não têm uma interface coerente. Um design adicional é necessário para mudar isso.

Consulta do Bazel e cquery

O Bazel query opera na fase de carregamento. Isso significa que ele não sabe quais sinalizações de linha de comando um alvo usa, pois essas as flags não são avaliadas até mais tarde no build (na fase de análise). Por isso, não é possível determinar quais ramificações select() são escolhidas.

O cquery do Bazel opera após a fase de análise do Bazel, então 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 superaproxima 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. Consulte Compatibilidade de regras para mais detalhes.

O principal problema 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. Confira 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({
        "//third_party/bazel_platforms/cpu:x86_32": "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({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "other string",
    }),
)

O build falha porque o sad_macro não consegue 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.

O build é bem-sucedido 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 mudar porque por definição, as macros são avaliadas antes de o Bazel ler as flags de linha de comando do build. Isso significa que não há informações suficientes para avaliar as funções select().

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 verdadeiro?

Porque macros (mas não regras) por definição não é possível 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().

Booleanos são um caso especial que falha silenciosamente, portanto você deve ser particularmente preste atenção a elas:

$ 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({
        "//third_party/bazel_platforms/cpu:x86_32": 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 avaliando é o próprio objeto select(). De acordo com Design Python padrão, todos os objetos com exceção de um número muito pequeno de exceções retornam "true" automaticamente.

Posso ler select() como um dict?

Macros não podem avaliar as seleções porque elas são avaliadas antes do Bazel saber quais são os parâmetros da linha de comando do build. Eles podem pelo menos ler o dicionário do 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, em seguida, inseri-lo em 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 = {
        "//third_party/bazel_platforms/cpu:x86_32": "x86 mode",
    },
)

$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX

Se você quiser oferecer suporte a select() e tipos 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()?

Em primeiro lugar, não use bind(). O uso de alias() foi descontinuado.

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

As regras de repo 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 avaliar para qualquer ramificação específica.

Em vez disso, use alias(), com um select() no o 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 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 vai mostrar a alternativa localizada em @alternative//:ssl.

Mas, sério, pare de usar bind().

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

Se //myapp:foo tiver uma 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 outro //bar de destino que dependa //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() de //myapp:foo. É possível inspecionar 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 diferentes configurações no mesmo build. Consulte a documentos do cquery (em inglês) para orientações sobre como usar somepath e um.

Por que 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() deve ser usado se a plataforma de destino tiver as restrições @platforms//cpu:x86 e @platforms//os:linux, mas não tiver 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 mais claro para os usuários quais plataformas atendem às condições desejadas.

E se eu quiser select na plataforma?

Se os requisitos de build exigirem especificamente a verificação da plataforma, você pode inverter o valor da flag --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. restringe excessivamente seu build e Confunde os usuários quando a condição esperada não é correspondente.