Yapılandırılabilir Derleme Özellikleri

Sorun bildir Kaynağı görüntüle Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Genellikle select() olarak bilinen yapılandırılabilir özellikler, kullanıcıların derleme kuralı özelliklerinin değerlerini komut satırında değiştirmesine olanak tanıyan bir Bazel özelliğidir.

Örneğin, mimariye uygun uygulamayı otomatik olarak seçen çok platformlu bir kitaplık veya derleme sırasında özelleştirilebilen, özellik yapılandırılabilir bir ikili dosya için kullanılabilir.

Örnek

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

Bu, komut satırındaki işaretlere göre bağımlılarını "seçen" bir cc_binary bildirir. Özellikle deps şu hale gelir:

Komut 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(), yapılandırma koşullarına göre seçilecek bir değerin yer tutucusu olarak kullanılır. Yapılandırma koşulları, config_setting hedeflerine referans veren etiketlerdir. Yapılandırılabilir bir özellikte select() kullanıldığında, farklı koşullar geçerli olduğunda özellik farklı değerler alır.

Eşleşmeler net olmalıdır: Birden fazla koşul eşleşiyorsa: * Tümü aynı değere çözümlenir. Örneğin, linux x86'da çalışırken her iki dal da "hello" olarak çözümlendiğinden {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} belirsiz değildir. * Birinin values, diğerlerinin hepsinin mutlak üst kümesidir. Örneğin, values = {"cpu": "x86", "compilation_mode": "dbg"} values = {"cpu": "x86"} öğesinin net bir uzmanlık alanıdır.

Yerleşik koşul //conditions:default, başka hiçbir koşul eşleşmediğinde otomatik olarak eşleşir.

Bu örnekte deps kullanılsa da select(), srcs, resources, cmd ve diğer birçok özellik için de aynı şekilde çalışır. Yalnızca az sayıda özellik yapılandırılamaz ve bunlar açıkça belirtilir. Örneğin, config_setting'nın kendi values özelliği yapılandırılamaz.

select() ve bağımlılıklar

Belirli özellikler, bir hedef altındaki tüm geçişli bağımlılıkların derleme parametrelerini değiştirir. Örneğin, genrule'nın tools, Bazel'in çalıştığı makinenin CPU'sunda --cpu değişiklikler yapar (çapraz derleme sayesinde bu, hedefin oluşturulduğu CPU'dan farklı olabilir). Bu işlem, yapılandırma geçişi olarak bilinir.

O dönemin

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

çalışıyor

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

x86 geliştirici makinesinde oluşturulan derleme, g_arm.src, tool1 ve x86tool.cc ile ilişkilendirilir. my_genrule'ye eklenen her iki select de my_genrule'nin --cpu=arm dahil olmak üzere derleme parametrelerini kullanır. tools özelliği, tool1 ve geçişli bağımlılıkları için --cpu olarak değişir.x86 select üzerinde tool1, --cpu=x86 dahil olmak üzere tool1'nin derleme parametrelerini kullanır.

Yapılandırma koşulları

Yapılandırılabilir bir özellikteki her anahtar, config_setting veya constraint_value için bir etiket referansıdır.

config_setting, yalnızca beklenen komut satırı işareti ayarlarından oluşan bir koleksiyondur. Bunları bir hedefte kapsülleyerek, kullanıcıların birden fazla yerden referans alabileceği "standart" koşulları kolayca koruyabilirsiniz.

constraint_value, çok platformlu davranış için destek sağlar.

Yerleşik işaretler

--cpu gibi işaretler Bazel'e yerleştirilmiştir: Derleme aracı, tüm projelerdeki tüm derlemeler için bu işaretleri doğal olarak anlar. Bunlar, config_setting öğesinin values özelliğiyle belirtilir:

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

flagN, işaret adıdır (-- olmadan, yani "--cpu" yerine "cpu"). valueN İlgili işaret için beklenen değerdir. :meaningful_condition_name, values içindeki her giriş eşleşirse eşleşir. Sıra önemli değildir.

valueN, komut satırında ayarlanmış gibi ayrıştırılır. Bunun anlamı şudur:

  • values = { "compilation_mode": "opt" } boyutu bazel build -c opt ifadesiyle eşleşiyor
  • values = { "force_pic": "true" } boyutu bazel build --force_pic=1 ifadesiyle eşleşiyor
  • values = { "force_pic": "0" } boyutu bazel build --noforce_pic ifadesiyle eşleşiyor

config_setting yalnızca hedef davranışını etkileyen işaretleri destekler. Örneğin, --show_progress yalnızca Bazel'in ilerleme durumunu kullanıcıya nasıl bildirdiğini etkilediği için izin verilmez. Hedefler, sonuçlarını oluşturmak için bu işareti kullanamaz. Desteklenen işaretlerin tam kümesi belgelenmemiştir. Pratikte, "mantıklı" olan çoğu işaretleme işe yarar.

Özel işaretler

Starlark derleme ayarları ile projeye özel kendi işaretlerinizi modelleyebilirsiniz. Yerleşik işaretlerin aksine bunlar derleme hedefleri olarak tanımlanır. Bu nedenle Bazel, bunlara hedef etiketleriyle referans verir.

Bunlar, config_setting'ın flag_values özelliğiyle tetiklenir:

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

Davranış, yerleşik işaretlerdeki davranışla aynıdır. Çalışan bir örnek için buraya bakın.

--define özel işaretler için alternatif bir eski söz dizimidir (örneğin --define foo=bar). Bu, values özelliğinde (values = {"define": "foo=bar"}) veya define_values özelliğinde (define_values = {"foo": "bar"}) ifade edilebilir. --define yalnızca geriye dönük uyumluluk için desteklenir. Mümkün olduğunda Starlark derleme ayarlarını tercih edin.

values, flag_values ve define_values bağımsız olarak değerlendirilir. config_setting, tüm değerler eşleşirse eşleşir.

Varsayılan koşul

Yerleşik koşul //conditions:default, başka koşul eşleşmediğinde eşleşir.

"Tam olarak bir eşleşme" kuralı nedeniyle, eşleşmesi ve varsayılan koşulu olmayan yapılandırılabilir bir özellik "no matching conditions" hatası verir. Bu, beklenmedik ayarlardan kaynaklanan sessiz hatalara karşı koruma sağlayabilir:

# 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

Hataları daha net bir şekilde görmek için select()no_match_error özelliğiyle özel mesajlar ayarlayabilirsiniz.

Platformlar

Komut satırında birden fazla işaret belirtme özelliği esneklik sağlasa da her hedef oluşturmak istediğinizde her birini ayrı ayrı ayarlamak zahmetli olabilir. Platformlar, bunları basit paketler halinde birleştirmenize olanak tanır.

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

Platform, komut satırında belirtilebilir. Bu işlev, platformun constraint_values alt kümesini içeren config_setting'ları etkinleştirerek bu config_setting'ların select() ifadelerinde eşleşmesini sağlar.

Örneğin, my_rocks öğesinin srcs özelliğini calcite.sh olarak ayarlamak için

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

Platformlar olmadan bu durum şöyle görünebilir:

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

select(), constraint_value'leri doğrudan da okuyabilir:

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

Bu sayede, yalnızca tek değerlere göre kontrol yapmanız gerektiğinde standart config_setting'lere gerek kalmaz.

Platformlar hâlâ geliştirme aşamasındadır. Ayrıntılar için belgeleri inceleyin.

select() birleştirme

select aynı özellikte birden fazla kez görünebilir:

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, başka bir select içinde görünemez. selects öğesini iç içe yerleştirmeniz gerekiyorsa ve özelliğiniz değer olarak başka hedefler alıyorsa ara hedef kullanın:

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

Birden fazla koşul eşleştiğinde select eşleşmesi gerekiyorsa VE zincirlemeyi kullanabilirsiniz.

VEYA zincirleme

Aşağıdakileri göz önünde bulundurun:

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

Çoğu koşul aynı bağımlılıkla değerlendirilir. Ancak bu söz diziminin okunması ve bakımı zordur. [":standard_lib"] işlemini birden fazla kez tekrarlamak zorunda kalmamak güzel olurdu.

Bir seçenek, değeri BUILD değişkeni olarak önceden tanımlamaktır:

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

Bu sayede bağımlılığı yönetmek kolaylaşır. Ancak yine de gereksiz kopyalamaya neden olur.

Daha doğrudan destek için aşağıdakilerden birini kullanın:

selects.with_or

Skylib'in selects modülündeki with_or makrosu, doğrudan select içinde OR koşullarını destekler:

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

Skylib'in selects modülündeki config_setting_group makrosu, birden fazla config_setting'ın ORlenmesini destekler:

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

selects.with_or aksine, farklı hedefler farklı özellikler arasında :config1_or_2 paylaşabilir.

Bir koşul diğerlerinin net bir "uzmanlığı" olmadığı veya tüm koşullar aynı değere çözümlenmediği sürece birden fazla koşulun eşleşmesi hatadır. Ayrıntılar için buraya bakın.

AND zincirleme

Birden fazla koşul eşleştiğinde select dalının eşleşmesi gerekiyorsa Skylib makrosu config_setting_group'u kullanın:

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

VEYA zincirlemenin aksine, mevcut config_setting'ler bir select içinde doğrudan AND yapılamaz. Bunları açıkça bir config_setting_group içine sarmalısınız.

Özel hata mesajları

Varsayılan olarak, hiçbir koşul eşleşmediğinde select() öğesinin bağlı olduğu hedef şu hatayla başarısız olur:

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

Bu ayar, no_match_error özelliğiyle özelleştirilebilir:

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

Kuralların uyumluluğu

Kural uygulamaları, yapılandırılabilir özelliklerin çözümlenmiş değerlerini alır. Örneğin, şu ifade verildiğinde:

# myapp/BUILD

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

Kural uygulama kodu, ctx.attr.some_attr değerini [":foo"] olarak görür.

Makrolar, select() koşullarını kabul edip yerel kurallara iletebilir. Ancak bu verileri doğrudan değiştiremezler. Örneğin, bir makronun

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

to

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

Bunun iki nedeni vardır.

İlk olarak, select'nın hangi yolu seçeceğini bilmesi gereken makrolar, işaret değerleri bilinmeden önce gerçekleşen Bazel'in yükleme aşamasında değerlendirildikleri için çalışamaz. Bu, Bazel'in temel tasarım kısıtlamasıdır ve yakın zamanda değişmesi beklenmemektedir.

İkincisi, tüm select yolları yinelemekten başka bir işlevi olmayan makrolar. Bu makrolar teknik olarak mümkün olsa da tutarlı bir kullanıcı arayüzüne sahip değildir. Bunu değiştirmek için daha fazla tasarım yapılması gerekir.

Bazel query ve cquery

Bazel query, Bazel'in yükleme aşamasında çalışır. Bu, hedefin hangi komut satırı işaretlerini kullandığını bilmediği anlamına gelir. Çünkü bu işaretler, derlemenin ilerleyen aşamalarında (analiz aşamasında) değerlendirilir. Bu nedenle, hangi select() dallarının seçildiğini belirleyemez.

Bazel cquery, Bazel'in analiz aşamasından sonra çalışır. Bu nedenle, tüm bu bilgilere sahiptir ve select()'leri doğru şekilde çözebilir.

Aşağıdakileri göz önünde bulundurun:

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, :my_lib'nin bağımlılıklarını fazla tahmin ediyor:

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

cquery ise tam bağımlılıklarını gösterir:

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

SSS

Neden select() makrolarda çalışmıyor?

select() kurallarda çalışır. Ayrıntılar için Kural uyumluluğu bölümüne bakın.

Bu sorunun genellikle işaret ettiği temel sorun, select() işlevinin makrolarda çalışmamasıdır. Bunlar kurallardan farklıdır. Farkı anlamak için kurallar ve makrolar ile ilgili dokümanlara bakın. Uçtan uca bir örnek:

Kural ve makro tanımlama:

# 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)

Kuralı ve makroyu oluşturun:

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

sad_macro, select() öğesini işleyemediğinden oluşturma işlemi başarısız oluyor:

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

sad_macro yorum satırı haline getirildiğinde derleme başarılı olur:

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

Tanım gereği makrolar, Bazel derlemenin komut satırı işaretlerini okumadan önce değerlendirildiğinden bu durum değiştirilemez. Bu, select() işlevlerini değerlendirmek için yeterli bilgi olmadığı anlamına gelir.

Ancak makrolar, select() değerlerini kurallara opak blob olarak iletebilir:

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

Neden select() her zaman doğru sonucu döndürüyor?

Makrolar (kurallar değil) tanım gereği select()'ları değerlendiremediğinden, bunu yapmaya yönelik herhangi bir girişim genellikle hataya neden olur:

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

Boole değerleri, sessizce başarısız olan özel bir durumdur. Bu nedenle, Boole değerleri konusunda özellikle dikkatli olmanız gerekir:

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

Bunun nedeni, makroların select() içeriğini anlamamasıdır. Bu nedenle, aslında select() nesnesinin kendisi değerlendirilir. Pythonic tasarım standartlarına göre, çok az sayıda istisna dışında tüm nesneler otomatik olarak doğru değerini döndürür.

Can I read select() like a dict?

Makrolar, Bazel derlemenin komut satırı parametrelerinin ne olduğunu bilmeden önce değerlendirme yaptığından select() işlevini değerlendiremez. En azından select() sözlüğünü okuyarak her değere son ek ekleyebilirler mi?

Bu, kavramsal olarak mümkündür ancak henüz bir Bazel özelliği değildir. Bugün yapabileceğiniz şey, düz bir sözlük hazırlayıp select()'ye aktarmaktır:

$ 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

Hem select() hem de yerel türleri desteklemek istiyorsanız şunları yapabilirsiniz:

$ 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 + "> $@",
    )

Neden select() işlevi bind() işleviyle çalışmıyor?

Öncelikle bind() kullanmayın. Bu politika kullanımdan kaldırılarak yerine alias() getirilmiştir.

Teknik yanıt, bind() öğesinin BUILD kuralı değil, depo kuralı olduğudur.

Depo kurallarının belirli bir yapılandırması yoktur ve BUILD kurallarıyla aynı şekilde değerlendirilmez. Bu nedenle, bind() içindeki bir select() aslında herhangi bir belirli dala değerlendirilemez.

Bunun yerine, bu tür bir çalışma zamanı belirleme işlemini gerçekleştirmek için actual özelliğinde select() ile birlikte alias() kullanmanız gerekir. alias() bir BUILD kuralı olduğundan ve belirli bir yapılandırmayla değerlendirildiğinden bu işlem doğru şekilde çalışır.

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

Bu kurulumla --define ssl_library=alternative değerini iletebilirsiniz. //:ssl veya //external:ssl değerine bağlı olan tüm hedefler, @alternative//:ssl konumundaki alternatifi görür.

Ancak bind() kullanmayı bırakmanız gerekir.

Neden select() işlevi beklediğim öğeyi seçmiyor?

//myapp:foo, beklediğiniz koşulu seçmeyen bir select() içeriyorsa hata ayıklamak için cquery ve bazel config kullanın:

Oluşturduğunuz üst düzey hedef //myapp:foo ise şunu çalıştırın:

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

Alt grafiğinde bir yerde //myapp:foo'ya bağlı başka bir hedef //bar oluşturuyorsanız şunu çalıştırın:

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

//myapp:foo'nin yanındaki (12e23b9a2b534a), //myapp:foo'nin select() sorununu çözen yapılandırmanın karma değeridir. bazel config ile değerlerini inceleyebilirsiniz:

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

Ardından bu çıktıyı her bir config_setting tarafından beklenen ayarlarla karşılaştırın.

//myapp:foo, aynı derlemede farklı yapılandırmalarda bulunabilir. Doğru somepath değerini almak için cquery belgelerini inceleyin.

select() neden platformlarla çalışmıyor?

Bazel, semantik net olmadığından belirli bir platformun hedef platform olup olmadığını kontrol eden yapılandırılabilir özellikleri desteklemez.

Örneğin:

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

Bu BUILD dosyasında, hedef platformda hem @platforms//cpu:x86 hem de @platforms//os:linux kısıtlamaları varsa ancak burada tanımlanan :x86_linux_platform değilse hangi select() kullanılmalıdır? BUILD dosyasının yazarı ve ayrı platformu tanımlayan kullanıcının farklı fikirleri olabilir.

Bunun yerine ne yapmalıyım?

Bunun yerine, şu kısıtlamalara sahip herhangi bir platformla eşleşen bir config_setting tanımlayın:

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

Bu süreç, belirli bir semantik tanımlar ve hangi platformların istenen koşulları karşıladığını kullanıcılar için daha net hale getirir.

Platformda gerçekten, gerçekten select yapmak istiyorsam ne olur?

Derleme koşullarınız özellikle platformun kontrol edilmesini gerektiriyorsa --platforms işaretinin değerini config_setting içinde değiştirebilirsiniz:

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

Bazel ekibi bunu yapmanızı önermez. Bu, derlemenizi aşırı derecede kısıtlar ve beklenen koşul eşleşmediğinde kullanıcıların kafasını karıştırır.