Atributos de compilación configurables

Denuncia un problema Ver fuente Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Los atributos configurables, comúnmente conocidos como select(), son una función de Bazel que permite a los usuarios activar o desactivar los valores. de atributos de reglas de compilación en la línea de comandos.

Esto se puede usar, por ejemplo, para una biblioteca multiplataforma que elija automáticamente la implementación adecuada para la arquitectura o para un objeto binario configurable de funciones que se pueda personalizar en el tiempo de compilación.

Ejemplo

# 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",
    },
)

Esto declara un cc_binary que "elige" sus dependencias según las marcas en la línea de comandos. Específicamente, deps se convierte en lo siguiente:

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() funciona como marcador de posición para un valor que se elegirá en función de las condiciones de configuración, que son etiquetas que hacen referencia a los objetivos config_setting. Cuando se usa select() en un atributo configurable, este adopta diferentes valores de manera eficaz cuando se cumplen diferentes condiciones.

Las coincidencias no deben ser ambiguas: si coinciden varias condiciones, puede ocurrir lo siguiente: * Todas se resuelven en el mismo valor. Por ejemplo, cuando se ejecuta en Linux x86, no hay ambigüedades. {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} porque ambas ramas se resuelven en “hello”. * El values de uno es un superconjunto estricto de todos los demás. Por ejemplo, values = {"cpu": "x86", "compilation_mode": "dbg"} es una especialización inequívoca de values = {"cpu": "x86"}.

La condición integrada //conditions:default coincide automáticamente cuando nada más.

Si bien en este ejemplo se usa deps, select() funciona igual de bien en srcs, resources, cmd y la mayoría de los demás atributos. Solo una pequeña cantidad de atributos no son configurables y están anotadas de forma clara. Por ejemplo, el atributo values de config_setting no se puede configurar.

select() y dependencias

Ciertos atributos cambian los parámetros de compilación para todas las dependencias transitivas en un objetivo. Por ejemplo, tools de genrule cambia --cpu a la CPU de la máquina que ejecuta Bazel (que, gracias a la compilación cruzada, puede ser diferente de la CPU para la que se compiló el destino). Esto se conoce como una transición de configuración.

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"],
    }),
)

activo

$ bazel build //myapp:my_genrule --cpu=arm

en una máquina de desarrollador x86 vincula la compilación a g_arm.src, tool1 y x86tool.cc. Ambos select adjuntos a my_genrule usan los parámetros de compilación de my_genrule, que incluyen --cpu=arm. El atributo tools cambia --cpu a x86 para tool1 y sus dependencias transitivas. El select en tool1 usa los parámetros de compilación de tool1, que incluyen --cpu=x86.

Condiciones de configuración

Cada clave en un atributo configurable es una referencia de etiqueta a un config_setting o constraint_value.

config_setting es solo una colección de la configuración esperada de la marca de línea de comandos. Cuando se encapsulan en un objetivo, fácil de mantener “estándar” condiciones a las que los usuarios pueden consultar desde varios lugares.

constraint_value proporciona compatibilidad con el comportamiento multiplataforma.

Marcas integradas

Las marcas como --cpu están integradas en Bazel: la herramienta de compilación las comprende de forma nativa para todas las compilaciones en todos los proyectos. Se especifican con De config_setting Atributo values:

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

flagN es un nombre de marca (sin --, por lo que es "cpu" en lugar de "--cpu"). valueN es el valor esperado para esa marca. :meaningful_condition_name coincide si cada entrada de values coincide. El orden es irrelevante.

valueN se analiza como si se hubiera establecido en la línea de comandos. Esto significa lo siguiente:

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

config_setting solo admite marcas que afectan el comportamiento objetivo. Por ejemplo, no se permite --show_progress porque solo afecta la forma en que Bazel informa el progreso al usuario. Los objetivos no pueden usar esa marca para construir sus resultados. El conjunto exacto de marcas admitidas documentados. En la práctica, la mayoría de las marcas que "tienen sentido" el trabajo.

Marcas personalizadas

Puedes modelar tus propias marcas específicas del proyecto con la configuración de compilación de Starlark. A diferencia de los indicadores integrados, estos son definidos como destinos de compilación, por lo que Bazel hace referencia a ellos con etiquetas de destino.

Se activan con los atributos config_setting flag_values atributo:

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

El comportamiento es el mismo que el de las marcas integradas. Ver aquí para ver un ejemplo funcional.

--define es una sintaxis heredada alternativa para marcas personalizadas (por ejemplo, --define foo=bar). Esto se puede expresar en el atributo values (values = {"define": "foo=bar"}) o en el atributo define_values (define_values = {"foo": "bar"}). --define solo es compatible con la retrocompatibilidad. Se prefiere la configuración de compilación de Starlark siempre que sea posible.

values, flag_values y define_values se evalúan de forma independiente. El config_setting coincide si todos los valores de todos coinciden.

La condición predeterminada

La condición integrada //conditions:default coincide cuando no hay ninguna otra condición. coincidencias.

Por la coincidencia "exactamente una coincidencia" regla, un atributo configurable sin coincidencias y ninguna condición predeterminada emite un error "no matching conditions". Esto puede Proteger contra fallas silenciosas de configuraciones 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 obtener errores aún más claros, puedes configurar mensajes personalizados con la política de select() no_match_error.

Plataformas

Si bien la capacidad de especificar varias marcas en la línea de comandos proporciona Además, puede ser agotador establecer cada uno individualmente cada vez en las que quieres crear un objetivo. Plataformas te permiten consolidarlos en paquetes 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",
    ],
)

Se puede especificar la plataforma en la línea de comandos. Activa la config_setting que contienen un subconjunto de constraint_values de la plataforma lo que permite que esos config_setting coincidan en expresiones select().

Por ejemplo, para establecer el atributo srcs de my_rocks en calcite.sh, haz lo siguiente: puedes simplemente ejecutar

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

Sin plataformas, esto podría parecerse a

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

select() también puede leer directamente constraint_value:

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"],
    }),
)

Esto evita la necesidad de config_settings de texto de plantilla cuando solo necesitas verificar valores individuales.

Las plataformas aún están en desarrollo. Consulta la documentación para obtener más información.

Combinación de select()

select puede aparecer varias veces en el mismo 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 no puede aparecer dentro de otro select. Si necesitas anidar selects Si el atributo toma otros objetivos como valores, usa un objetivo intermedio:

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"],
        ...
    }),
)

Si necesitas que un select coincida cuando varias condiciones coincidan, considera la cadena AND.

Cadena OR

Ten en cuenta lo siguiente:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": [":standard_lib"],
        ":config2": [":standard_lib"],
        ":config3": [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

La mayoría de las condiciones se evalúan como la misma dependencia. Pero esta sintaxis es difícil de leer y mantener. Sería bueno no tener que repetir [":standard_lib"] varias veces. veces.

Una opción es definir previamente el valor como una variable 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"],
    }),
)

Esto facilita la administración de la dependencia. Pero aun así genera causas la duplicación.

Para obtener asistencia más directa, usa una de las siguientes opciones:

selects.with_or

El with_or en una macro de Skylib selects El módulo admite condiciones de ORing directamente en una 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

El config_setting_group en una macro de Skylib selects El módulo admite ORs múltiples config_setting:

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"],
    }),
)

A diferencia de selects.with_or, diferentes objetivos pueden compartir :config1_or_2 en diferentes atributos.

Es un error de coincidencia de varias condiciones, a menos que una de ellas no sea ambigua "especialización" de las demás o todas se resuelven con el mismo valor. Consulta los detalles aquí.

Cadena AND

Si necesitas que una rama select coincida cuando coincidan varias condiciones, usa la 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"],
    }),
)

A diferencia de la encadenación O, los config_setting existentes no se pueden AND directamente dentro de un select. Debes unirlas de forma explícita en un config_setting_group.

Mensajes de error personalizados

De forma predeterminada, cuando no se cumple ninguna condición, el destino al que se adjunta select() falla con el siguiente error:

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

Esto se puede personalizar con el 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

Compatibilidad de las reglas

Las implementaciones de reglas reciben los valores resueltos de las reglas atributos. Por ejemplo, dado lo siguiente:

# myapp/BUILD

some_rule(
    name = "my_target",
    some_attr = select({
        ":foo_mode": [":foo"],
        ":bar_mode": [":bar"],
    }),
)
$ bazel build //myapp/my_target --define mode=foo

El código de implementación de reglas ve ctx.attr.some_attr como [":foo"].

Las macros pueden aceptar cláusulas select() y pasarlas a aplicaciones nativas las reglas de firewall. Sin embargo, no pueden manipularlos directamente. Por ejemplo, no hay manera para que una macro convierta

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

to

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

Esto es por dos razones.

Primero, las macros que necesitan saber qué ruta elegirá un select no funcionan porque las macros se evalúan en la fase de carga de Bazel que ocurre antes de que se conozcan los valores de las marcas. Esta es una restricción de diseño principal de Bazel que es poco probable que cambie pronto.

En segundo lugar, las macros que solo necesitan iterar sobre todas las rutas de select, si bien son técnicamente factibles, carecen de una IU coherente. Se necesita más diseño para cambiar esto.

Consulta de Bazel y cquery

Bazel query opera sobre la base de datos fase de carga. Esto significa que no sabe qué marcas de línea de comandos usa un destino, ya que esas marcas no se evalúan hasta más adelante en la compilación (en la fase de análisis). Por lo tanto, no puede determinar qué ramas de select() se eligen.

Bazel cquery opera después de la fase de análisis de Bazel, por lo que tiene toda esta información y puede resolver con precisión los select().

Considera lo siguiente:

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 sobreestima las dependencias de :my_lib:

$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep

mientras que cquery muestra sus dependencias exactas:

$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep

Preguntas frecuentes

¿Por qué select() no funciona en macros?

select() funciona en las reglas. Consulta Compatibilidad de reglas para obtener más detalles.

El problema clave que suele significar esta pregunta es que select() no funciona en macros. Estas son diferentes de las reglas. Consulta la documentación sobre reglas y macros para comprender la diferencia. A continuación, se muestra un ejemplo completo:

Define una regla y una 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)

Crea una instancia de la regla y la 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",
    }),
)

La compilación falla porque sad_macro no puede procesar el 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.

La compilación se realiza correctamente cuando marcas como comentario 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.

Esto es imposible de cambiar porque, por definición, las macros se evalúan antes de que Bazel lea las marcas de línea de comandos de la compilación. Eso significa que no hay información suficiente para evaluar select().

Sin embargo, las macros pueden pasar objetos select() como BLOB opacos a las reglas:

# 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 qué select() siempre muestra un valor verdadero?

Porque son macros (pero no reglas) por definición no se pueden evaluar las select(), ningún intento de hacerlo suele producir un error:

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().

Los booleanos son un caso especial que fallan silenciosamente, así que deberías ser estén atentos:

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

Esto sucede porque las macros no comprenden el contenido de select(). Por lo tanto, lo que realmente evalúan es el objeto select(). Según los estándares de diseño pythonic, todos los objetos, excepto una cantidad muy pequeña de excepciones, muestran automáticamente el valor verdadero.

¿Puedo leer select() como un diccionario?

Las macros no pueden evaluar selecciones porque lo hacen antes Bazel sabe cuáles son los parámetros de la línea de comandos de la compilación. ¿Pueden, al menos, leer el diccionario de select() para, por ejemplo, agregar un sufijo a cada valor?

Conceptualmente, esto es posible, pero todavía no es una función de Bazel. Lo que puedes hacer hoy es preparar un diccionario sencillo y, luego, ingresarlo en un 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

Si deseas admitir tipos select() y nativos, puedes hacer lo siguiente:

$ 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 qué select() no funciona con bind()?

En primer lugar, no uses bind(). Dejó de estar disponible y se reemplazó por alias().

La respuesta técnica es que bind() es una regla de repo, no una regla de compilación.

Las reglas de repositorio no tienen una configuración específica y no se evalúan de la misma manera que las reglas de BUILD. Por lo tanto, un select() en una bind() no puede evaluar en cualquier rama específica.

En su lugar, debes usar alias(), con un select() en el atributo actual, para realizar este tipo de determinación del tiempo de ejecución. Esta funciona correctamente, ya que alias() es una regla de COMPILACIÓN y se evalúa con un configuración 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",
    }),
)

Con esta configuración, puedes pasar --define ssl_library=alternative, y cualquier objetivo que dependa de //:ssl o //external:ssl verá la alternativa ubicada en @alternative//:ssl.

Pero, en serio, deja de usar bind().

¿Por qué mi select() no elige lo que espero?

Si //myapp:foo tiene un select() que no elige la condición que esperas, Usa cquery y bazel config para depurar:

Si //myapp:foo es el objetivo de nivel superior que estás compilando, ejecuta lo siguiente:

$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)

Si compilas algún otro //bar de destino que dependa de //myapp:foo en algún lugar de su subgrafo, ejecuta lo siguiente:

$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar   (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)

El (12e23b9a2b534a) junto a //myapp:foo es un hash del de Terraform que resuelve el select() de //myapp:foo. Puedes inspeccionar su valores con 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]
  ...
}
...

Luego, compara este resultado con la configuración que espera cada config_setting.

//myapp:foo puede existir en diferentes configuraciones en la misma compilación. Consulta los documentos de cquery para obtener orientación sobre el uso de somepath y obtener el correcto.

¿Por qué select() no funciona con las plataformas?

Bazel no admite atributos configurables que verifiquen si una plataforma determinada es la plataforma de destino porque la semántica no es clara.

Por ejemplo:

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": [],
    }),
)

En este archivo BUILD, que select() debe usarse si la plataforma de destino tiene @platforms//cpu:x86 y @platforms//os:linux, pero no es la ¿:x86_linux_platform se definió aquí? Es posible que el autor del archivo BUILD y el usuario que definió la plataforma independiente tengan ideas diferentes.

¿Qué otra opción debería usar?

En cambio, define un config_setting que coincida con cualquier plataforma con estas restricciones:

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": [],
    }),
)

Este proceso define una semántica específica y aclara a los usuarios qué plataformas cumplan con las condiciones deseadas.

¿Qué sucede si de verdad quiero select en la plataforma?

Si tus requisitos de compilación requieren específicamente que se verifique la plataforma, puedes invertir el valor de la marca --platforms en un 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": [],
    }),
)

El equipo de Bazel no recomienda hacerlo, ya que restringe demasiado tu compilación y confunde a los usuarios cuando la condición esperada no coincide.