कॉन्फ़िगर किए जा सकने वाले बिल्ड एट्रिब्यूट

समस्या की शिकायत करें सोर्स देखें

कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट को आम तौर पर select() कहा जाता है. यह एक Bazel सुविधा है. इसकी मदद से, कमांड लाइन पर बिल्ड के नियम वाली विशेषताओं की वैल्यू को टॉगल किया जा सकता है.

उदाहरण के लिए, इसका इस्तेमाल ऐसी मल्टीप्लैटफ़ॉर्म लाइब्रेरी के लिए किया जा सकता है जो अपने-आप आर्किटेक्चर के लिए सही लागू करने की सेटिंग चुनती है. इसके अलावा, यह सुविधा-कॉन्फ़िगर करने लायक ऐसी बाइनरी के लिए भी इस्तेमाल की जा सकती है जिसे बिल्ड के समय पसंद के मुताबिक बनाया जा सकता है.

उदाहरण

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

यह cc_binary एलान करता है कि कमांड लाइन में मौजूद फ़्लैग के आधार पर, यह अपने डेटा को "चुनता है". खास तौर पर, deps यह बन जाता है:

आदेश 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 टारगेट को रेफ़र करने वाले लेबल होती हैं. कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट में select() का इस्तेमाल करने से, अलग-अलग शर्तें एक साथ होने पर एट्रिब्यूट, अलग-अलग वैल्यू को असरदार तरीके से अपनाता है.

मैच साफ़ तौर पर दिखने चाहिए: अगर कई शर्तें मैच करती हैं, तो दोनों में से किसी एक * की वैल्यू एक ही होनी चाहिए. उदाहरण के लिए, Linux x86 पर चलते समय, {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} में साफ़ तौर पर जानकारी नहीं दिखती, क्योंकि दोनों ब्रांच में सिर्फ़ "हैलो" होता है. * किसी का values, बाकी सभी लोगों का सुपरसेट है. उदाहरण के लिए, values = {"cpu": "x86", "compilation_mode": "dbg"}, values = {"cpu": "x86"} की एक विशेषज्ञता है.

पहले से मौजूद शर्त //conditions:default अपने-आप तब मैच करती है, जब कुछ और नहीं.

इस उदाहरण में deps का इस्तेमाल किया गया है, लेकिन select() srcs, resources, cmd, और दूसरे ज़्यादातर एट्रिब्यूट पर भी ठीक उसी तरह काम करता है. सिर्फ़ कुछ एट्रिब्यूट को कॉन्फ़िगर नहीं किया जा सकता. साथ ही, इनके बारे में साफ़ तौर पर जानकारी दी गई है. उदाहरण के लिए, config_setting के अपने values एट्रिब्यूट को कॉन्फ़िगर नहीं किया जा सकता.

select() और डिपेंडेंसी

कुछ एट्रिब्यूट किसी टारगेट के तहत सभी ट्रांज़िटिव डिपेंडेंसी के लिए बिल्ड पैरामीटर में बदलाव करते हैं. उदाहरण के लिए, genrule का tools, --cpu को Bazel चलाने वाली मशीन के सीपीयू से बदल देता है (जो क्रॉस-कंपाइलेशन की वजह से, टारगेट बनाए गए सीपीयू से अलग हो सकता है). इसे कॉन्फ़िगरेशन ट्रांज़िशन कहा जाता है.

दिया गया

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

चालू

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

x86 डेवलपर मशीन पर, बिल्ड को g_arm.src, tool1, और x86tool.cc से बाइंड करता है. my_genrule से जुड़े दोनों select, my_genrule के बिल्ड पैरामीटर का इस्तेमाल करते हैं, जिनमें --cpu=arm शामिल होता है. tool1 और इसकी ट्रांज़िटिव डिपेंडेंसी के लिए, tools एट्रिब्यूट, --cpu को x86 में बदल देता है. tool1 का select, tool1 के बिल्ड पैरामीटर का इस्तेमाल करता है, जिनमें --cpu=x86 शामिल हैं.

कॉन्फ़िगरेशन की शर्तें

कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट में मौजूद हर कुंजी, config_setting या constraint_value का लेबल रेफ़रंस होती है.

config_setting, कमांड लाइन फ़्लैग की अनुमानित सेटिंग का एक कलेक्शन है. इन्हें टारगेट में शामिल करके, उन "स्टैंडर्ड" स्थितियों को बनाए रखना आसान होता है जिन्हें उपयोगकर्ता कई जगहों से इस्तेमाल कर सकते हैं.

constraint_value, मल्टी-प्लैटफ़ॉर्म व्यवहार के लिए सहायता देता है.

पहले से मौजूद फ़्लैग

--cpu जैसे फ़्लैग, Bazel में बनाए गए हैं: बिल्ड टूल सभी प्रोजेक्ट में मौजूद सभी बिल्ड के लिए, इन फ़्लैग को मूल रूप से समझता है. इनके बारे में config_setting के values एट्रिब्यूट का इस्तेमाल करके बताया गया है:

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

flagN एक फ़्लैग नाम है (-- के बिना, इसलिए "--cpu" की बजाय "cpu"). valueN उस फ़्लैग के लिए अनुमानित मान है. अगर values में दर्ज हर एंट्री का मिलान होता है, तो :meaningful_condition_name मैच करता है. ऑर्डर काम का नहीं है.

valueN को इस तरह पार्स किया जाता है जैसे उसे कमांड लाइन पर सेट किया गया हो. इसका मतलब है:

  • values = { "compilation_mode": "opt" } bazel build -c opt से मेल खाता है
  • values = { "force_pic": "true" } bazel build --force_pic=1 से मेल खाता है
  • values = { "force_pic": "0" } bazel build --noforce_pic से मेल खाता है

config_setting सिर्फ़ उन फ़्लैग के साथ काम करता है जो टारगेट व्यवहार पर असर डालते हैं. उदाहरण के लिए, --show_progress की अनुमति नहीं है, क्योंकि इससे सिर्फ़ इस बात पर असर पड़ता है कि Bazel, उपयोगकर्ता को किस तरह की रिपोर्ट देगा. टारगेट अपने नतीजे बनाने के लिए उस फ़्लैग का इस्तेमाल नहीं कर सकते. इस्तेमाल किए जा सकने वाले फ़्लैग के सटीक सेट का दस्तावेज़ उपलब्ध नहीं है. व्यावहारिक रूप से, "काम की बात" वाले ज़्यादातर फ़्लैग काम करते हैं.

पसंद के मुताबिक फ़्लैग

Starlark की बिल्ड सेटिंग की मदद से, अपने प्रोजेक्ट के हिसाब से फ़्लैग बनाएं. पहले से मौजूद फ़्लैग के उलट, इन्हें बिल्ड टारगेट के तौर पर तय किया जाता है. इसलिए, Bazel टारगेट लेबल के साथ इनका रेफ़रंस देता है.

ये config_setting के flag_values एट्रिब्यूट से ट्रिगर होते हैं:

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

उपयोगकर्ता का व्यवहार, पहले से मौजूद फ़्लैग जैसा ही होता है. काम करने वाला उदाहरण देखने के लिए यहां देखें.

--define कस्टम फ़्लैग के लिए एक वैकल्पिक लेगसी सिंटैक्स है, जैसे कि --define foo=bar. इसे values एट्रिब्यूट (values = {"define": "foo=bar"}) या define_values एट्रिब्यूट (define_values = {"foo": "bar"}) में दिखाया जा सकता है. --define का इस्तेमाल, सिर्फ़ पुराने सिस्टम के साथ काम करने की सुविधा के लिए किया जा सकता है. जब भी हो सके, Starlark बिल्ड सेटिंग को प्राथमिकता दें.

values, flag_values, और define_values अलग-अलग आकलन करते हैं. सभी वैल्यू मेल खाने पर, config_setting मैच करेगा.

डिफ़ॉल्ट स्थिति

पहले से मौजूद //conditions:default शर्त तब मैच करती है, जब कोई दूसरी शर्त मेल नहीं खाती.

"एग्ज़ैक्ट मैच है" नियम की वजह से, कॉन्फ़िगर किया जा सकने वाला ऐसा एट्रिब्यूट जिसकी कोई वैल्यू मैच नहीं होती और कोई डिफ़ॉल्ट स्थिति नहीं सेट करने पर, "no matching conditions" गड़बड़ी मिलती है. ऐसा करने से, अनचाही सेटिंग की वजह से बिना आवाज़ के काम करने की सुविधा को बंद होने से रोका जा सकता है:

# 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

ज़्यादा साफ़ गड़बड़ियों के लिए, आपके पास select() के no_match_error एट्रिब्यूट की मदद से कस्टम मैसेज सेट करने का विकल्प है.

प्लैटफ़ॉर्म

हालांकि, कमांड लाइन पर एक से ज़्यादा फ़्लैग तय करने की सुविधा से सुविधा मिलती है, लेकिन जब भी आपको कोई टारगेट बनाना हो, तो हर एक फ़्लैग को अलग-अलग सेट करना मुश्किल हो सकता है. प्लैटफ़ॉर्म की मदद से, इन्हें आसान बंडल में इकट्ठा किया जा सकता है.

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

कमांड लाइन पर प्लैटफ़ॉर्म के बारे में बताया जा सकता है. यह ऐसे config_setting को चालू करता है जिनमें प्लैटफ़ॉर्म के constraint_values का सबसेट शामिल होता है, जिससे उन config_setting का select() एक्सप्रेशन में मिलान होता है.

उदाहरण के लिए, my_rocks के srcs एट्रिब्यूट को calcite.sh पर सेट करने के लिए, आसानी से

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

प्लैटफ़ॉर्म के बिना, यह कुछ ऐसा लग सकता है

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

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

इससे बॉयलरप्लेट config_settings की ज़रूरत तब बचती है, जब आपको सिर्फ़ एक वैल्यू की जांच करनी होती है.

प्लैटफ़ॉर्म पर अभी काम चल रहा है. ज़्यादा जानकारी के लिए, दस्तावेज़ देखें.

select()से

select को एक ही एट्रिब्यूट में कई बार दिखाया जा सकता है:

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, दूसरे select के अंदर नहीं दिख सकता. अगर आपको selects को नेस्ट करना है और आपका एट्रिब्यूट दूसरे टारगेट को वैल्यू के तौर पर लेता है, तो इंटरमीडिएट टारगेट का इस्तेमाल करें:

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

अगर कई शर्तों के मैच होने पर आपको select की ज़रूरत है, तो और चेन को जोड़ने के बारे में सोचें.

OR चेन

नीचे दी गई चीज़ों पर विचार करें:

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

ज़्यादातर शर्तों का मूल्यांकन एक ही डीप पर किया जाता है. हालांकि, इस सिंटैक्स को पढ़ना और बनाए रखना मुश्किल है. [":standard_lib"] को बार-बार न दोहराना बेहतर होगा.

एक विकल्प यह है कि वैल्यू को 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"],
    }),
)

इससे डिपेंडेंसी को मैनेज करना आसान हो जाता है. लेकिन इससे अब भी ग़ैर-ज़रूरी डुप्लिकेशन होता है.

सीधे तौर पर सहायता पाने के लिए, इनमें से किसी एक विकल्प का इस्तेमाल करें:

selects.with_or

Skylib के selects मॉड्यूल में मौजूद with_or मैक्रो, ORing की शर्तों को सीधे 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

Skylib के config_setting_group मॉड्यूल में मौजूद मैक्रो selects मॉड्यूल में ORएक से ज़्यादा 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"],
    }),
)

selects.with_or से अलग, अलग-अलग टारगेट अलग-अलग एट्रिब्यूट के साथ :config1_or_2 शेयर कर सकते हैं.

यह कई शर्तों को पूरा करने में गड़बड़ी है, जब तक कि कोई एक, दूसरी शर्तों की साफ़ तौर पर "विशेषज्ञता" न हो या वे सभी एक ही मान पर न ले जाते हों. ज़्यादा जानकारी के लिए यहां देखें.

AND चेन करना

अगर कई शर्तों के मैच होने पर आपको select ब्रांच को मैच करना है, तो 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"],
    }),
)

OR चेन के उलट, मौजूदा config_setting को सीधे select में AND नहीं किया जा सकता. आपको उन्हें साफ़ तौर पर, config_setting_group में रैप करना होगा.

कस्टम गड़बड़ी के मैसेज

डिफ़ॉल्ट रूप से, कोई भी शर्त मेल न खाने पर, select() टारगेट से जुड़ा हुआ गड़बड़ी के साथ फ़ेल हो जाता है:

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

इसे 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

नियम के साथ काम करने की सुविधा

नियम लागू करने के लिए कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट के हल की गई वैल्यू मिलती हैं. उदाहरण के लिए, दिया गया:

# myapp/BUILD

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

नियम लागू करने वाले कोड को ctx.attr.some_attr [":foo"] के तौर पर दिखता है.

मैक्रो select() क्लॉज़ को स्वीकार कर सकते हैं और उन्हें नेटिव नियमों में भेज सकते हैं. हालांकि, वे सीधे तौर पर उनके साथ हेर-फेर नहीं कर सकते. उदाहरण के लिए, मैक्रो को बदलने का कोई तरीका नहीं है

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

से

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

इसकी दो वजहें हैं.

सबसे पहले, मैक्रो का आकलन करने के लिए, उन्हें यह पता होना चाहिए कि select कौनसा पाथ चुनेगा, काम नहीं कर सकता. ऐसा इसलिए, क्योंकि मैक्रो का आकलन, Bazel के लोडिंग फ़ेज़ में होता है. यह आकलन, फ़्लैग की वैल्यू पता चलने से पहले होता है. यह Bazel के डिज़ाइन से जुड़ी मुख्य पाबंदी है. इसे जल्द ही बदला नहीं जा सकता.

दूसरा, ऐसे मैक्रो जिन्हें सभी select पाथ दोहराना है, लेकिन तकनीकी रूप से संभव है, लेकिन उनमें बेहतर यूज़र इंटरफ़ेस (यूआई) नहीं होता. इसे बदलने के लिए और डिज़ाइन की ज़रूरत है.

Bazel क्वेरी और cquery

Bazel query, Bazel के लोडिंग फ़ेज़ पर काम करता है. इसका मतलब है कि इसे यह नहीं पता होता कि टारगेट किस कमांड लाइन फ़्लैग का इस्तेमाल करता है. इसकी वजह यह है कि बिल्ड में (विश्लेषण के चरण में) बाद तक उन फ़्लैग का आकलन नहीं किया जाता है. इसलिए, यह पता नहीं चल सकता कि कौनसी select() ब्रांच चुनी गई हैं.

Bazel cquery, Bazel के विश्लेषण के बाद काम करता है. इसलिए, इसके पास यह पूरी जानकारी होती है और यह select()s का सही तरीके से समाधान कर सकती है.

इन बातों पर ध्यान दें:

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 की डिपेंडेंसी से ज़्यादा अनुमान लगाता है:

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

जबकि cquery अपनी सटीक डिपेंडेंसी दिखाता है:

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

अक्सर पूछे जाने वाले सवाल

मैक्रो में Select() काम क्यों नहीं करता?

Select() नियमों में काम करता है! ज़्यादा जानकारी के लिए, नियमों के साथ काम करने के नियम देखें.

आम तौर पर, इस सवाल का मतलब यह है कि मैक्रो में Select() काम नहीं करता है. ये नियम से अलग हैं. अंतर को समझने के लिए नियम और मैक्रो का दस्तावेज़ देखें. यहां शुरू से अंत तक का एक उदाहरण दिया गया है:

एक नियम और मैक्रो तय करें:

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

नियम और मैक्रो को इंस्टैंशिएट करें:

# 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() को प्रोसेस नहीं कर सका:

$ 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 पर टिप्पणी करते हैं, तो बिल्डिंग काम करती है:

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

इसे बदला नहीं जा सकता, क्योंकि परिभाषा के मुताबिक मैक्रो का आकलन, बिल्ड के कमांड लाइन फ़्लैग को पढ़ने से पहले किया जाता है. इसका मतलब है कि selected()s का आकलन करने के लिए काफ़ी जानकारी नहीं है.

हालांकि, मैक्रो select()s को नियमों के लिए ओपेक ब्लॉब के तौर पर पास कर सकते हैं:

# 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() हमेशा 'सही' क्यों दिखाता है?

परिभाषा के मुताबिक मैक्रो (लेकिन नियम नहीं) select() का आकलन नहीं कर सकते, इसलिए ऐसा करने की कोई भी कोशिश आम तौर पर गड़बड़ी पैदा करती है:

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

बूलियन एक विशेष मामला है जो बिना किसी सूचना के विफल हो जाता है, इसलिए आपको विशेष रूप से उनके साथ सावधान रहना चाहिए:

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

ऐसा इसलिए होता है, क्योंकि मैक्रो select() के कॉन्टेंट को नहीं समझते. इसलिए, वे select() ऑब्जेक्ट का असल में आकलन कर रहे हैं. Python के डिज़ाइन स्टैंडर्ड के मुताबिक, कुछ अपवादों को छोड़कर सभी ऑब्जेक्ट अपने-आप 'सही' दिखाता है.

क्या किसी डिक्शनरी की तरह Select() पढ़ा जा सकता है?

मैक्रो, चुने गए विकल्पों का आकलन नहीं कर सकते, क्योंकि बज़ल को पता होने से पहले मैक्रो आकलन करता है कि बिल्ड के कमांड लाइन पैरामीटर क्या हैं. क्या वे हर वैल्यू में सफ़िक्स जोड़ने के लिए, कम से कम select() की डिक्शनरी को पढ़ सकते हैं?

सैद्धान्तिक तौर पर यह मुमकिन है, लेकिन फ़िलहाल यह Bazel सुविधा नहीं है. आज आप एक सीधे शब्दकोश तैयार करके, उसे 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 = {
        "//tools/target_cpu:x86": "x86 mode",
    },
)

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

अगर आपको select() और नेटिव टाइप, दोनों का इस्तेमाल करना है, तो यह करें:

$ 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() काम क्यों नहीं करता?

सबसे पहले, bind() इस्तेमाल न करें. हालांकि, अब यह alias() के पक्ष में है.

तकनीकी जवाब यह है कि bind() रेपो नियम है, न कि बिल्ड नियम.

रेपो नियमों का कोई खास कॉन्फ़िगरेशन नहीं होता और उनका मूल्यांकन BUILD नियमों की तरह नहीं किया जाता. इसलिए, bind() में select() किसी खास ब्रांच का आकलन नहीं कर सकता.

इसके बजाय, इस तरह के रन-टाइम तय करने के लिए, आपको actual एट्रिब्यूट में select() के साथ alias() का इस्तेमाल करना चाहिए. यह सही तरीके से काम करता है, क्योंकि alias() एक बिल्ड नियम है और इसका आकलन एक खास कॉन्फ़िगरेशन के साथ किया जाता है.

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

इस सेटअप की मदद से, --define ssl_library=alternative को पास किया जा सकता है और //:ssl या //external:ssl पर निर्भर किसी भी टारगेट को @alternative//:ssl पर मौजूद विकल्प दिखेगा.

लेकिन, bind() का इस्तेमाल बंद कर दें.

मेरा Select() यह क्यों नहीं चुनता कि मैं क्या उम्मीद करता/करती हूं?

अगर //myapp:foo में select() है, जो आपकी उम्मीद के मुताबिक स्थिति नहीं चुनता है, तो डीबग करने के लिए cquery और bazel config का इस्तेमाल करें:

अगर //myapp:foo टॉप-लेवल टारगेट बना रहे हैं, तो चलाएं:

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

अगर कोई दूसरा टारगेट //bar बनाया जा रहा है, जो इसके सबग्राफ़ में कहीं भी //myapp:foo पर निर्भर है, तो चलाएं:

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

//myapp:foo के बगल में मौजूद (12e23b9a2b534a), कॉन्फ़िगरेशन का हैश है. यह //myapp:foo के select() को रिज़ॉल्व करता है. 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]
  ...
}
...

इसके बाद, इस आउटपुट की तुलना हर config_setting के लिए अनुमानित सेटिंग से करें.

//myapp:foo एक ही बिल्ड में अलग-अलग कॉन्फ़िगरेशन में मौजूद हो सकता है. somepath के इस्तेमाल के बारे में दिशा-निर्देश पाने के लिए, cquery दस्तावेज़ देखें.

select(), प्लैटफ़ॉर्म के साथ काम क्यों नहीं करता?

Bazel को कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट पर, यह जांच करने की सुविधा नहीं है कि दिया गया प्लैटफ़ॉर्म टारगेट प्लैटफ़ॉर्म है या नहीं.

उदाहरण के लिए:

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

इस BUILD फ़ाइल में, अगर टारगेट प्लैटफ़ॉर्म में @platforms//cpu:x86 और @platforms//os:linux, दोनों सीमाएं हैं, लेकिन यहां बताई गई :x86_linux_platform नहीं हैं, तो किस select() का इस्तेमाल किया जाना चाहिए? BUILD फ़ाइल के लेखक और अलग प्लैटफ़ॉर्म बनाने वाले उपयोगकर्ता के आइडिया अलग-अलग हो सकते हैं.

ऐसे में, मुझे क्या करना चाहिए?

इसके बजाय, ऐसा config_setting तय करें जो इन पाबंदियों वाले किसी भी प्लैटफ़ॉर्म से मेल खाता हो:

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

यह प्रोसेस खास सिमैंटिक के बारे में बताती है. इससे उपयोगकर्ताओं को पता चलता है कि कौनसे प्लैटफ़ॉर्म ज़रूरी शर्तों को पूरा करते हैं.

अगर मुझे वाकई प्लैटफ़ॉर्म पर select इस्तेमाल करना हो, तो क्या होगा?

अगर बिल्ड की ज़रूरी शर्तों के लिए प्लैटफ़ॉर्म की जांच करना ज़रूरी हो, तो --platforms फ़्लैग की वैल्यू को 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": [],
    }),
)

Bazel टीम ऐसा करने का समर्थन नहीं करती है; यह आपके बिल्ड को बहुत ज़्यादा सीमित कर देती है और सही स्थिति के मेल न खाने पर उपयोगकर्ताओं को भ्रमित कर देती है.