Configuraciones

En esta página, se explican los beneficios y el uso básico de las configuraciones de Starlark, la API de Bazel para personalizar la forma en que se compila tu proyecto. Incluye cómo definir la configuración de compilación y proporciona ejemplos.

Esto permite lo siguiente:

  • Definir marcas personalizadas para tu proyecto, lo que hace que ya no sea necesario usar --define
  • Escribir transiciones para configurar dependencias en diferentes configuraciones que sus superiores (como --compilation_mode=opt o --cpu=arm)
  • Incorporar mejores valores predeterminados en las reglas (como compilar automáticamente //my:android_app con un SDK especificado)

y mucho más, todo desde archivos .bzl (no se requiere una versión de Bazel). Consulta el bazelbuild/examples repositorio para ejemplos.

Configuración de compilación definida por el usuario

Un parámetro de configuración de compilación es una sola pieza de información de configuración. Piensa en una configuración como un mapa de clave-valor. Si configuras --cpu=ppc y --copt="-DFoo", se produce una configuración que se ve como {cpu: ppc, copt: "-DFoo"}. Cada entrada es un parámetro de configuración de compilación.

Las marcas tradicionales como cpu y copt son parámetros de configuración nativos: sus claves se definen y sus valores se establecen dentro del código Java de Bazel nativo. Los usuarios de Bazel solo pueden leerlos y escribirlos a través de la línea de comandos y otras APIs que se mantienen de forma nativa. Para cambiar las marcas nativas y las APIs que las exponen, se requiere una versión de Bazel. Los parámetros de configuración de compilación definidos por el usuario se definen en archivos .bzl (y, por lo tanto, no necesitan una versión de Bazel para registrar los cambios). También se pueden configurar a través de la línea de comandos (si se designan como flags, consulta más abajo), pero también se pueden configurar a través de transiciones definidas por el usuario.

Cómo definir la configuración de compilación

Ejemplo de extremo a extremo

El parámetro build_setting rule()

Los parámetros de configuración de compilación son reglas como cualquier otra y se diferencian con el atributo build_setting de la función rule()de Starlark.

# example/buildsettings/build_settings.bzl
string_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True)
)

El atributo build_setting toma una función que designa el tipo de parámetro de configuración de compilación. El tipo se limita a un conjunto de tipos básicos de Starlark, como bool y string. Consulta la documentación del módulo config para obtener más detalles. Se pueden realizar tipos más complicados en la función de implementación de la regla. Más información a continuación.

Las funciones del módulo config toman un parámetro booleano opcional, flag, que se establece en falso de forma predeterminada. Si flag se establece en verdadero, los usuarios pueden configurar el parámetro de configuración de compilación en la línea de comandos, así como los escritores de reglas de forma interna a través de valores y transiciones. No todos los parámetros de configuración deben poder establecerse por los usuarios. Por ejemplo, si como escritor de reglas tienes algún modo de depuración que te gustaría activar dentro de las reglas de prueba, no querrás darles a los usuarios la capacidad de activar esa función de forma indiscriminada dentro de otras reglas que no sean de prueba.

Cómo usar ctx.build_setting_value

Al igual que todas las reglas, las reglas de configuración de compilación tienen funciones de implementación. Se puede acceder al valor de tipo Starlark básico de los parámetros de configuración de compilación a través del ctx.build_setting_value método. Este método solo está disponible para los objetos ctx de las reglas de configuración de compilación. Estos métodos de implementación pueden reenviar directamente el valor de configuración de compilación o realizar un trabajo adicional en él, como la verificación de tipos o la creación de estructuras más complejas. A continuación, se muestra cómo implementar un enum-typed build setting:

# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])

temperatures = ["HOT", "LUKEWARM", "ICED"]

def _impl(ctx):
    raw_temperature = ctx.build_setting_value
    if raw_temperature not in temperatures:
        fail(str(ctx.label) + " build setting allowed to take values {"
             + ", ".join(temperatures) + "} but was set to unallowed value "
             + raw_temperature)
    return TemperatureProvider(type = raw_temperature)

temperature = rule(
    implementation = _impl,
    build_setting = config.string(flag = True)
)

Cómo definir marcas de cadena de varios conjuntos

La configuración de cadenas tiene un parámetro allow_multiple adicional que permite que la marca se establezca varias veces en la línea de comandos o en bazelrcs. Su valor predeterminado aún se establece con un atributo de tipo cadena:

# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
    name = "roasts",
    build_setting_default = "medium"
)

Cada configuración de la marca se trata como un valor único:

$ bazel build //my/target --//example:roasts=blonde \
    --//example:roasts=medium,dark

Lo anterior se analiza como {"//example:roasts": ["blonde", "medium,dark"]} y ctx.build_setting_value muestra la lista ["blonde", "medium,dark"].

Cómo crear instancias de parámetros de configuración de compilación

Las reglas definidas con el parámetro build_setting tienen un atributo obligatorio implícito build_setting_default. Este atributo toma el mismo tipo que el declarado por el build_setting parámetro.

# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])

def _impl(ctx):
    return FlavorProvider(type = ctx.build_setting_value)

flavor = rule(
    implementation = _impl,
    build_setting = config.string(flag = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
    name = "favorite_flavor",
    build_setting_default = "APPLE"
)

Configuración predefinida

Ejemplo de extremo a extremo

La Skylib de Skylib incluye un conjunto de parámetros de configuración predefinidos que puedes crear instancias sin tener que escribir Starlark personalizado.

Por ejemplo, para definir un parámetro de configuración que acepte un conjunto limitado de valores de cadena, haz lo siguiente:

# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
    name = "myflag",
    values = ["a", "b", "c"],
    build_setting_default = "a",
)

Para obtener una lista completa, consulta Reglas comunes de configuración de compilación.

Cómo usar la configuración de compilación

Dependiendo de la configuración de compilación

Si un destino desea leer una parte de la información de configuración, puede depender directamente del parámetro de configuración de compilación a través de una dependencia de atributo normal.

# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
    if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
        ...

drink_rule = rule(
    implementation = _rule_impl,
    attrs = {
        "flavor": attr.label()
    }
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
    name = "favorite_flavor",
    build_setting_default = "APPLE"
)
drink_rule(
    name = "my_drink",
    flavor = ":favorite_flavor",
)

Es posible que los lenguajes deseen crear un conjunto canónico de parámetros de configuración de compilación de los que dependan todas las reglas de ese lenguaje. Si bien el concepto nativo de fragments ya no existe como un objeto codificado en el mundo de la configuración de Starlark, una forma de traducir este concepto sería usar conjuntos de atributos implícitos comunes. Por ejemplo:

# kotlin/rules.bzl
_KOTLIN_CONFIG = {
    "_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
    "_mode": attr.label(default = "//kotlin/config:mode-flag"),
    ...
}

...

kotlin_library = rule(
    implementation = _rule_impl,
    attrs = dicts.add({
        "library-attr": attr.string()
    }, _KOTLIN_CONFIG)
)

kotlin_binary = rule(
    implementation = _binary_impl,
    attrs = dicts.add({
        "binary-attr": attr.label()
    }, _KOTLIN_CONFIG)

Cómo usar la configuración de compilación en la línea de comandos

Al igual que la mayoría de las marcas nativas, puedes usar la línea de comandos para establecer parámetros de configuración de compilación que estén marcados como marcas. El nombre del parámetro de configuración de compilación es su ruta de destino completa con la sintaxis name=value:

$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed

Se admite la sintaxis booleana especial:

$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag

Cómo usar alias de configuración de compilación

Puedes establecer un alias para la ruta de destino de tu parámetro de configuración de compilación para que sea más fácil de leer en la línea de comandos. Los alias funcionan de manera similar a las marcas nativas y también hacen uso de la sintaxis de opción de doble guion.

Para establecer un alias, agrega --flag_alias=ALIAS_NAME=TARGET_PATH a tu .bazelrc . Por ejemplo, para establecer un alias en coffee, haz lo siguiente:

# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp

Práctica recomendada: Si estableces un alias varias veces, tendrá prioridad el más reciente. Usa nombres de alias únicos para evitar resultados de análisis no deseados.

Para usar el alias, escríbelo en lugar de la ruta de destino del parámetro de configuración de compilación. Con el ejemplo anterior de coffee establecido en el .bazelrc del usuario, haz lo siguiente:

$ bazel build //my/target --coffee=ICED

en lugar de

$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED

Práctica recomendada: Si bien es posible establecer alias en la línea de comandos, dejarlos en un .bazelrc reduce el desorden de la línea de comandos.

Parámetros de configuración de compilación con tipo de etiqueta

Ejemplo de extremo a extremo

A diferencia de otros parámetros de configuración de compilación, los parámetros de configuración con tipo de etiqueta no se pueden definir con el build_setting parámetro de regla. En cambio, Bazel tiene dos reglas integradas: label_flag y label_setting. Estas reglas reenvían los proveedores del destino real al que se establece el parámetro de configuración de compilación. label_flag y label_setting pueden leerse/escribirse mediante transiciones y label_flag puede establecerse por el usuario como otras reglas build_setting. La única diferencia es que no se pueden definir de forma personalizada.

Los parámetros de configuración con tipo de etiqueta reemplazarán, en algún momento, la funcionalidad de los valores predeterminados de vinculación tardía. Los atributos predeterminados de vinculación tardía son atributos con tipo de etiqueta cuyos valores finales pueden verse afectados por la configuración. En Starlark, esto reemplazará la configuration_field API.

# example/rules.bzl
MyProvider = provider(fields = ["my_field"])

def _dep_impl(ctx):
    return MyProvider(my_field = "yeehaw")

dep_rule = rule(
    implementation = _dep_impl
)

def _parent_impl(ctx):
    if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
        ...

parent_rule = rule(
    implementation = _parent_impl,
    attrs = { "my_field_provider": attr.label() }
)

# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")

dep_rule(name = "dep")

parent_rule(name = "parent", my_field_provider = ":my_field_provider")

label_flag(
    name = "my_field_provider",
    build_setting_default = ":dep"
)

Configuración de compilación y select()

Ejemplo de extremo a extremo

Los usuarios pueden configurar atributos en los parámetros de configuración de compilación con select(). Los destinos de configuración de compilación se pueden pasar al atributo flag_values de config_setting. El valor que debe coincidir con la configuración se pasa como un String y, luego, se analiza al tipo del parámetro de configuración de compilación para que coincida.

config_setting(
    name = "my_config",
    flag_values = {
        "//example:favorite_flavor": "MANGO"
    }
)

Transiciones definidas por el usuario

Una transición de configuración asigna la transformación de un destino configurado a otro dentro del gráfico de compilación.

Las reglas que las establecen deben incluir un atributo especial:

  "_allowlist_function_transition": attr.label(
      default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
  )

Si agregas transiciones, puedes aumentar fácilmente el tamaño de tu gráfico de compilación. Esto establece una lista de entidades permitidas en los paquetes en los que puedes crear destinos de esta regla. El valor predeterminado en el bloque de código anterior permite todo. Sin embargo, si deseas restringir quién usa tu regla, puedes configurar ese atributo para que apunte a tu propia lista de entidades permitidas personalizada. Comunícate con bazel-discuss@googlegroups.com si deseas obtener asesoramiento o asistencia para comprender cómo las transiciones pueden afectar el rendimiento de tu compilación.

Definición

Las transiciones definen cambios de configuración entre reglas. Por ejemplo, una solicitud como "compilar mi dependencia para una CPU diferente a la de su superior" se controla mediante una transición.

Formalmente, una transición es una función de una configuración de entrada a una o más configuraciones de salida. La mayoría de las transiciones son 1:1, como "anular la configuración de entrada con --cpu=ppc". También pueden existir transiciones 1:2+, pero tienen restricciones especiales.

En Starlark, las transiciones se definen de manera similar a las reglas, con una función transition() definitoria y una función de implementación.

# example/transitions/transitions.bzl
def _impl(settings, attr):
    _ignore = (settings, attr)
    return {"//example:favorite_flavor" : "MINT"}

hot_chocolate_transition = transition(
    implementation = _impl,
    inputs = [],
    outputs = ["//example:favorite_flavor"]
)

La función transition() toma una función de implementación, un conjunto de parámetros de configuración de compilación para leer(inputs) y un conjunto de parámetros de configuración de compilación para escribir (outputs). La función de implementación tiene dos parámetros: settings y attr. settings es un diccionario {String:Object} de todos los parámetros de configuración declarados en el parámetro inputs para transition().

attr es un diccionario de atributos y valores de la regla a la que se adjunta la transición. Cuando se adjuntan como una transición de arista saliente, los valores de estos atributos se configuran después de la resolución de select(). Cuando se adjuntan como una transición de arista entrante, attr no incluye ningún atributo que use un selector para resolver su valor. Si una transición de arista entrante en --foo lee el atributo bar y, luego, también selecciona --foo para establecer el atributo bar, existe la posibilidad de que la transición de arista entrante lea el valor incorrecto de bar en la transición.

La función de implementación debe mostrar un diccionario (o una lista de diccionarios, en el caso de transiciones con varias configuraciones de salida) de los nuevos valores de configuración de compilación que se aplicarán. Los conjuntos de claves de diccionario que se muestran deben contener exactamente el conjunto de parámetros de configuración de compilación que se pasa al outputs parámetro de la función de transición. Esto es cierto incluso si un parámetro de configuración de compilación no se cambia durante la transición; su valor original debe pasarse de forma explícita en el diccionario que se muestra.

Cómo definir transiciones 1:2+

Ejemplo de extremo a extremo

La transición de arista saliente puede asignar una sola configuración de entrada a dos o más configuraciones de salida. Esto es útil para definir reglas que agrupan código de varias arquitecturas.

Las transiciones 1:2+ se definen mostrando una lista de diccionarios en la función de implementación de transición.

# example/transitions/transitions.bzl
def _impl(settings, attr):
    _ignore = (settings, attr)
    return [
        {"//example:favorite_flavor" : "LATTE"},
        {"//example:favorite_flavor" : "MOCHA"},
    ]

coffee_transition = transition(
    implementation = _impl,
    inputs = [],
    outputs = ["//example:favorite_flavor"]
)

También pueden establecer claves personalizadas que la función de implementación de reglas puede usar para leer dependencias individuales:

# example/transitions/transitions.bzl
def _impl(settings, attr):
    _ignore = (settings, attr)
    return {
        "Apple deps": {"//command_line_option:cpu": "ppc"},
        "Linux deps": {"//command_line_option:cpu": "x86"},
    }

multi_arch_transition = transition(
    implementation = _impl,
    inputs = [],
    outputs = ["//command_line_option:cpu"]
)

Cómo adjuntar transiciones

Ejemplo de extremo a extremo

Las transiciones se pueden adjuntar en dos lugares: aristas entrantes y aristas salientes. En efecto, esto significa que las reglas pueden hacer la transición de su propia configuración (transición de arista entrante ) y de las configuraciones de sus dependencias (transición de arista saliente ).

NOTA: Actualmente, no hay forma de adjuntar transiciones de Starlark a reglas nativas. Si necesitas hacerlo, comunícate con bazel-discuss@googlegroups.com para obtener ayuda para encontrar soluciones alternativas.

Transiciones de arista entrante

Las transiciones de arista entrante se activan adjuntando un objeto transition (creado por transition()) al parámetro rule()'s cfg:

# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
    implementation = _impl,
    cfg = hot_chocolate_transition,
    ...

Las transiciones de arista entrante deben ser transiciones 1:1.

Transiciones de arista saliente

Las transiciones de arista saliente se activan adjuntando un objeto transition (creado por transition()) al parámetro cfg de un atributo:

# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
    implementation = _impl,
    attrs = { "dep": attr.label(cfg = coffee_transition)}
    ...

Las transiciones de arista saliente pueden ser 1:1 o 1:2+.

Consulta Cómo acceder a los atributos con transiciones para obtener información sobre cómo leer estas claves.

Transiciones en opciones nativas

Ejemplo de extremo a extremo

Las transiciones de Starlark también pueden declarar lecturas y escrituras en opciones de configuración de compilación nativas a través de un prefijo especial para el nombre de la opción.

# example/transitions/transitions.bzl
def _impl(settings, attr):
    _ignore = (settings, attr)
    return {"//command_line_option:cpu": "k8"}

cpu_transition = transition(
    implementation = _impl,
    inputs = [],
    outputs = ["//command_line_option:cpu"]

Opciones nativas no admitidas

Bazel no admite la transición en --define con "//command_line_option:define". En su lugar, usa un parámetro de configuración de compilación personalizado . En general, no se recomiendan los usos nuevos de --define en favor de los parámetros de configuración de compilación.

Bazel no admite la transición en --config. Esto se debe a que --config es una marca de "expansión" que se expande a otras marcas.

Es fundamental que --config pueda incluir marcas que no afecten la configuración de compilación, como --spawn_strategy . Por diseño, Bazel no puede vincular esas marcas a destinos individuales. Esto significa que no hay una forma coherente de aplicarlas en las transiciones.

Como solución alternativa, puedes detallar explícitamente las marcas que son parte de la configuración en tu transición. Esto requiere mantener la expansión de --config's en dos lugares, lo que es un defecto conocido de la IU.

Transiciones en permitir varios parámetros de configuración de compilación

Cuando se establecen parámetros de configuración de compilación que permiten varios valores, el valor del parámetro de configuración debe establecerse con una lista.

# example/buildsettings/build_settings.bzl
string_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
    # Using a value of just "dark" here will throw an error
    return {"//example:roasts" : ["dark"]},

coffee_transition = transition(
    implementation = _transition_impl,
    inputs = [],
    outputs = ["//example:roasts"]
)

Transiciones no operativas

Si una transición muestra {}, [] o None, es una abreviatura para mantener todos los parámetros de configuración en sus valores originales. Esto puede ser más conveniente que establecer explícitamente cada salida en sí misma.

# example/transitions/transitions.bzl
def _impl(settings, attr):
    _ignore = (attr)
    if settings["//example:already_chosen"] is True:
      return {}
    return {
      "//example:favorite_flavor": "dark chocolate",
      "//example:include_marshmallows": "yes",
      "//example:desired_temperature": "38C",
    }

hot_chocolate_transition = transition(
    implementation = _impl,
    inputs = ["//example:already_chosen"],
    outputs = [
        "//example:favorite_flavor",
        "//example:include_marshmallows",
        "//example:desired_temperature",
    ]
)

Cómo acceder a los atributos con transiciones

Ejemplo de extremo a extremo

Cuando se adjunta una transición a una arista saliente (independientemente de si la transición es 1:1 o 1:2+), ctx.attr se fuerza a ser una lista si aún no lo es. No se especifica el orden de los elementos en esta lista.

# example/transitions/rules.bzl
def _transition_impl(settings, attr):
    return {"//example:favorite_flavor" : "LATTE"},

coffee_transition = transition(
    implementation = _transition_impl,
    inputs = [],
    outputs = ["//example:favorite_flavor"]
)

def _rule_impl(ctx):
    # Note: List access even though "dep" is not declared as list
    transitioned_dep = ctx.attr.dep[0]

    # Note: Access doesn't change, other_deps was already a list
    for other_dep in ctx.attr.other_deps:
      # ...


coffee_rule = rule(
    implementation = _rule_impl,
    attrs = {
        "dep": attr.label(cfg = coffee_transition)
        "other_deps": attr.label_list(cfg = coffee_transition)
    })

Si la transición es 1:2+ y establece claves personalizadas, ctx.split_attr se puede usar para leer dependencias individuales para cada clave:

# example/transitions/rules.bzl
def _impl(settings, attr):
    _ignore = (settings, attr)
    return {
        "Apple deps": {"//command_line_option:cpu": "ppc"},
        "Linux deps": {"//command_line_option:cpu": "x86"},
    }

multi_arch_transition = transition(
    implementation = _impl,
    inputs = [],
    outputs = ["//command_line_option:cpu"]
)

def _rule_impl(ctx):
    apple_dep = ctx.split_attr.dep["Apple deps"]
    linux_dep = ctx.split_attr.dep["Linux deps"]
    # ctx.attr has a list of all deps for all keys. Order is not guaranteed.
    all_deps = ctx.attr.dep

multi_arch_rule = rule(
    implementation = _rule_impl,
    attrs = {
        "dep": attr.label(cfg = multi_arch_transition)
    })

Consulta el ejemplo completo aquí.

Integración con plataformas y cadenas de herramientas

Muchas marcas nativas actuales, como --cpu y --crosstool_top, están relacionadas con la resolución de la cadena de herramientas. En el futuro, es probable que las transiciones explícitas en estos tipos de marcas se reemplacen por la transición en la plataforma de destino.

Consideraciones sobre la memoria y el rendimiento

Agregar transiciones y, por lo tanto, configuraciones nuevas a tu compilación tiene un costo: gráficos de compilación más grandes, gráficos de compilación menos comprensibles y compilaciones más lentas. Vale la pena tener en cuenta estos costos cuando consideres usar transiciones en tus reglas de compilación. A continuación, se muestra un ejemplo de cómo una transición podría crear un crecimiento exponencial de tu gráfico de compilación.

Compilaciones con comportamiento inadecuado: un caso práctico

Gráfico de escalabilidad

Figura 1: Gráfico de escalabilidad que muestra un destino de nivel superior y sus dependencias

Este gráfico muestra un destino de nivel superior, //pkg:app, que depende de dos destinos, a //pkg:1_0 y //pkg:1_1. Ambos destinos dependen de dos destinos, //pkg:2_0 y //pkg:2_1. Ambos destinos dependen de dos destinos, //pkg:3_0 y //pkg:3_1. Esto continúa hasta //pkg:n_0 y //pkg:n_1, que dependen de un solo destino, //pkg:dep.

La compilación de //pkg:app requiere \(2n+2\) destinos:

  • //pkg:app
  • //pkg:dep
  • //pkg:i_0 y //pkg:i_1 para \(i\) en \([1..n]\)

Imagina que implementas una marca --//foo:owner=<STRING> y se aplica //pkg:i_b.

depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"

En otras palabras, //pkg:i_b agrega b al valor anterior de --owner para todas sus dependencias.

Esto produce los siguientes destinos configurados:

//pkg:app                              //foo:owner=""
//pkg:1_0                              //foo:owner=""
//pkg:1_1                              //foo:owner=""
//pkg:2_0 (via //pkg:1_0)              //foo:owner="0"
//pkg:2_0 (via //pkg:1_1)              //foo:owner="1"
//pkg:2_1 (via //pkg:1_0)              //foo:owner="0"
//pkg:2_1 (via //pkg:1_1)              //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0)  //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1)  //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0)  //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1)  //foo:owner="11"
...

//pkg:dep produce \(2^n\) destinos configurados: config.owner= "\(b_0b_1...b_n\)" para todos los elementos \(b_i\) en \(\{0,1\}\).

Esto hace que el gráfico de compilación sea exponencialmente más grande que el gráfico de destino, con las consecuencias correspondientes en la memoria y el rendimiento.

TODO: Agrega estrategias para la medición y la mitigación de estos problemas.

Lecturas adicionales

Para obtener más detalles sobre la modificación de las configuraciones de compilación, consulta lo siguiente: