Yapılandırılabilir Derleme Özellikleri

Sorun bildir Kaynağı göster

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

Bu, örneğin, mimari için uygun uygulamayı otomatik olarak seçen çok platformlu bir kitaplık veya derleme sırasında özelleştirilebilen, özellik yapılandırılabilen bir ikili program 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 tanımlarını "seçen" bir cc_binary tanımlar. Daha ayrıntılı olarak belirtmek gerekirse deps şu şekilde olur:

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(), config_setting hedeflerine referans veren etiketler olan yapılandırma koşullarına göre seçilecek bir değer için yer tutucu işlevi görür. Yapılandırılabilir bir özellikte select() kullanıldığında özellik, farklı koşullar geçerli olduğunda farklı değerleri etkili şekilde benimser.

Eşleşmeler açık olmalıdır: Birden fazla koşul eşleşirse iki koşuldan biri eşleşir. * Hepsi aynı değere çözümlenir. Örneğin, linux x86'da çalışırken her iki dal da "hello" değerine çözümlendiği için bu değer kesin olmayan {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} olur. * Birinin values özelliği, diğer tüm öğelerin katı bir üst kümesidir. Örneğin values = {"cpu": "x86", "compilation_mode": "dbg"}, values = {"cpu": "x86"} için açık bir uzmanlıktır.

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

Bu örnekte deps kullanılsa da select(), srcs, resources, cmd ve diğer birçok özellikte de iyi performans gösterir. Az sayıda özellik yapılandırılabilir değildir ve bu özellikler açıkça ek açıklamalarla gösterilir. Örneğin, config_setting özelliğinin 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 için derleme parametrelerini değiştirir. Örneğin, genrule'in tools öğesi, --cpu değerini Bazel'i çalıştıran makinenin CPU'su ile değiştirir (çapraz derleme sayesinde bu işlem, hedefin oluşturulduğu CPU'dan farklı olabilir). Bu, 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 derlemeyi g_arm.src, tool1 ve x86tool.cc bağlar. my_genrule öğesine ekli select öğelerinin ikisi de --cpu=arm dahil olmak üzere my_genrule derleme parametrelerini kullanır. tools özelliği, tool1 ve geçişli bağımlılıkları için --cpu değerini x86 olarak değiştirir. tool1 sitesindeki select, --cpu=x86 dahil olmak üzere tool1 derleme parametrelerini kullanır.

Yapılandırma koşulları

Yapılandırılabilir özellikteki her anahtar, bir config_setting veya constraint_value etiketine referanstır.

config_setting, beklenen komut satırı işareti ayarlarının bir koleksiyonudur. Bunları bir hedefe kapsülleyerek, kullanıcıların birden çok yerden başvurabileceği "standart" koşulların sağlanması kolaydır.

constraint_value, çok platformlu davranışı destekler.

Yerleşik işaretler

--cpu gibi işaretler Bazel'de yerleşiktir: Derleme aracı, bunları tüm projelerdeki tüm derlemeler için yerel olarak anlar. Bunlar config_setting values özelliğiyle belirtilir:

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

flagN bir işaret adıdır (-- olmadan, bu nedenle "--cpu" yerine "cpu"). valueN bu işaret için beklenen değerdir. values içindeki her giriş eşleşirse :meaningful_condition_name eşleşir. Sıra alakasız.

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ışı etkileyen işaretleri destekler. Örneğin, yalnızca Bazel'in kullanıcıya ilerleme durumunu bildirme şeklini etkilediği için --show_progress öğesine izin verilmez. Hedefler, sonuçlarını oluşturmak için bu işareti kullanamaz. Desteklenen işaret gruplarının tamamı belgelenmemiştir. Pratikte, "anlamlı" olan çoğu işaret işe yarar.

Özel işaretler

Starlark derleme ayarlarıyla kendi projeye özgü işaretlerinizi modelleyebilirsiniz. Yerleşik işaretlerin aksine, bunlar derleme hedefi olarak tanımlanır. Bu nedenle, Bazel bunlara hedef etiketlerle referans verir.

Bunlar, config_setting flag_values özelliğiyle tetiklenir:

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

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

--define, özel işaretler için alternatif olan eski bir 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. Hepsindeki tüm değerler eşleşiyorsa config_setting eşleşir.

Varsayılan koşul

Başka hiçbir koşul eşleşmediğinde //conditions:default yerleşik koşulu eşleşir.

"Tam bir eşleşme" kuralı nedeniyle, eşleşmesi olmayan ve varsayılan koşulu olmayan yapılandırılabilir özellikler "no matching conditions" hatası verir. Bu, beklenmeyen ayarlardan kaynaklanan sessiz hatalara karşı korunabilir:

# 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

Daha da net hatalar için select() özelliğinin no_match_error özelliğiyle özel mesajlar ayarlayabilirsiniz.

Platformlar

Komut satırında birden fazla işaret belirtebilme olanağı esneklik sağlasa da, her hedef oluşturmak istediğinizde her birini ayrı ayrı ayarlamak külfetli olabilir. Platformlar bunları basit gruplar halinde birleştirmenizi sağlar.

# 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. Platformun constraint_values alt kümesini içeren config_setting öğelerini etkinleştirerek bu config_setting'lerin select() ifadeleriyle eşleşmesini sağlar.

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

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

Platformlar olmadan şöyle görünebilir:

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

select(), constraint_value dosyalarını 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 tekli değerlerle kontrol yapmanız gerektiğinde standart config_setting ihtiyacını ortadan kaldırır.

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

select() birleştiriliyor

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 diğer hedefleri değer olarak alırsa 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 eşleşme için bir select öğesine ihtiyacınız varsa VE zincirlemeyi kullanmayı düşünün.

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ı davranışı değerlendirir. Ancak bu söz diziminin okunması ve kullanılması zordur. [":standard_lib"] öğesini birden fazla kez tekrarlamak zorunda kalmamak iyi olur.

Seçeneklerden biri, 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ığı daha kolay bir şekilde yönetebilirsiniz. Ancak yine de gereksiz yinelemelere 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 bir select içinde ORoluşturma 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, ORbirden fazla config_setting'yı 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 işlevinin aksine farklı hedefler, :config1_or_2 özelliğini farklı özellikler arasında paylaşabilir.

Bir koşul diğerlerinin açık bir "uzmanlığı" olmadığı veya tümü aynı değere çözüm olmadığı sürece birden fazla koşulun eşleşmesi hata teşkil eder. Ayrıntılar için buraya göz atın.

VE zincirleme

Birden fazla koşul eşleştiğinde bir select dalına ihtiyacınız varsa Skylib makrosunu 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 zincirinin aksine, mevcut config_setting öğeleri doğrudan bir select içinde ANDkıslanamaz. Bunları açıkça bir config_setting_group ile sarmalamanız gerekir.

Özel hata mesajları

Varsayılan olarak, hiçbir koşul eşleşmediğinde select() öğesinin eklendiği 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, 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 uyumluluğu

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

# 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() ifadelerini kabul edebilir ve bunları yerel kurallara iletebilir. Ancak bilgileri doğrudan manipüle edemezler. Örneğin, bir makronun elde etmek istediğiniz

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

-

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

Bunun iki nedeni vardır.

Öncelikle, makrolar Bazel'in işaret değerleri bilinmeden önce gerçekleşen yükleme aşamasında değerlendirildiğinden bir select öğesinin hangi yolu seçeceğini bilmesi gereken makrolar çalışamaz. Bu, yakın zamanda değişmesi muhtemel olmayan temel Bazel tasarım kısıtlamasıdır.

İkinci olarak, teknik olarak uygun olsa da yalnızca tüm select yollarında yinelemesi gereken makrolar tutarlı bir kullanıcı arayüzüne sahip değildir. Bunu değiştirmek için daha fazla tasarım gereklidir.

Bazel sorgusu ve cquery

Bazel query, Bazel'in yükleme aşamasında çalışır. Yani bir hedefin hangi komut satırı işaretlerini kullandığını bilemez, çünkü bu işaretlemeler derlemenin sonraki aşamalarına kadar (analiz aşamasında) değerlendirilmez. Bu nedenle hangi select() dalının seçildiği belirlenemez.

Bazel cquery, Bazel'in analiz aşamasından sonra çalıştığı için tüm bu bilgileri içerir ve select() sorunlarını doğru bir şekilde çözebilir.

Seçenek:

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 bağımlılarının fazla yakınına denk geliyor:

$ 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

Select() neden 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 anlamına geldiğ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 belgelere bakın. Aşağıda uçtan uca bir örnek verilmiştir:

Kural ve makro tanımlayın:

# 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 somutlaştırın:

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

sad_macro, select() öğesini işleyemediği için derleme başarısız oldu:

$ 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 adlı kullanıcıya yorum yaptığınızda oluşturma işlemi 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ği için bu değiştirilemez. Bu, select() işlevini değerlendirmek için yeterli bilgi olmadığı anlamına gelir.

Ancak makrolar select()'leri 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.

Select() işlevi neden her zaman doğru sonucu döndürüyor?

Makrolar (kurallar değil) tanımı gereği select()'ları değerlendiremediğinden, bu amaçla yapılan herhangi bir girişim genellikle bir 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'lar, sessizce başarısız olan özel bir durumlardır. Bu nedenle, Boole'lar konusunda özellikle dikkatli olmalısınız:

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

Bunun nedeni, makroların select() içeriğini anlamamasıdır. Aslında select() nesnesini değerlendiriyorlar. Python tasarım standartlarına göre, çok az sayıda istisna dışındaki tüm nesneler otomatik olarak doğru değerini döndürür.

Select() işlevini bir dikt gibi okuyabilir miyim?

Makrolar, derlemenin komut satırı parametrelerinin ne olduğunu anlamadan önce makrolar değerlendirildiği için seçili öğeleri değerlendiremez. Örneğin, her değere son ek eklemek için en azından select() sözlüğünü okuyabilirler mi?

Kavramsal olarak bu mümkündür, ancak henüz bir Bazel özelliği değildir. Bugün yapabilirsiniz: düz bir sözlük hazırlayıp bunu bir select() dosyasına ekleyebilirsiniz:

$ 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

Hem select() hem de yerel türleri desteklemek istiyorsanız şunu 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 + "> $@",
    )

Select() neden bind() ile çalışmıyor?

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

Teknik yanıt, bind() DERLEME kuralı değil, depo kuralıdır.

Depo kurallarının belirli bir yapılandırması yoktur ve DERLEME kurallarıyla aynı şekilde değerlendirilmez. Bu nedenle, bir bind() içindeki select() öğesi belirli bir dalı değerlendiremez.

Bunun yerine, bu tür bir çalışma zamanı belirlemesi yapmak için actual özelliğinde select() ile birlikte alias() kullanmanız gerekir. alias() bir DERLEME kuralı olduğu ve belirli bir yapılandırmayla değerlendirildiği için bu yöntem doğru şekilde çalışır.

Gerekirse bir alias() için bind() hedef noktası bile oluşturabilirsiniz.

$ 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 atlayabilirsiniz. Bu durumda, //:ssl veya //external:ssl öğesine bağlı tüm hedefler @alternative//:ssl adresinde bulunan alternatifi görür.

Ama gerçekten bind() kullanmayı bırakın.

Select() işlevim beklediğimi neden 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:

Derlediğiniz ü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 öğesine bağlı başka bir hedef //bar oluşturuyorsanız şu komutu ç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 öğesinin yanındaki (12e23b9a2b534a), //myapp:foo select() özelliğini çözümleyen yapılandırmanın karmasıdır. Değerlerini bazel config ile 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 çıkışı, her bir config_setting için beklenen ayarlarla karşılaştırın.

//myapp:foo aynı derlemede farklı yapılandırmalarda mevcut olabilir. Doğru olanı bulmak için somepath kullanmayla ilgili yol gösterici bilgiler için cquery belgelerine bakın.

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

Bazel, anlamlar 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 platformun hem @platforms//cpu:x86 hem de @platforms//os:linux kısıtlamaları varsa ancak burada tanımlı :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.

Onun yerine ne yapmalıyım?

Bunun yerine, aşağıdaki kısıtlamalarla 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 anlamları tanımlayarak hangi platformların istenen koşulları karşıladığını kullanıcılara daha net bir şekilde açıklar.

Ya gerçekten platformda select.

Derleme ihtiyaçlarınız özellikle platformun kontrol edilmesini gerektiriyorsa config_setting içindeki --platforms işaretinin değerini 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 yapmayı desteklemez. Bu yaklaşım derlemenizi fazlasıyla kısıtlar ve beklenen koşul eşleşmediğinde kullanıcıların kafasını karıştırır.