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

कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट को आम तौर पर 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"} क्योंकि दोनों ब्रांच की वैल्यू "hello" है. * एक की values, बाकी सभी की वैल्यू का सुपरसेट होनी चाहिए. उदाहरण के लिए, values = {"cpu": "x86", "compilation_mode": "dbg"} , values = {"cpu": "x86"} का साफ़ तौर पर मैच करने वाला वर्शन है.

जब कोई और शर्त मैच नहीं करती, तो //conditions:default वाली बिल्ट-इन शर्त अपने-आप मैच हो जाती है.

इस उदाहरण में deps का इस्तेमाल किया गया है. हालांकि, select() का इस्तेमाल srcs, resources, cmd, और ज़्यादातर अन्य एट्रिब्यूट पर भी किया जा सकता है. सिर्फ़ कुछ एट्रिब्यूट कॉन्फ़िगर नहीं किए जा सकते. इनके बारे में साफ़ तौर पर बताया गया है. उदाहरण के लिए, config_setting's अपना 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 शामिल है. tools एट्रिब्यूट, tool1 और उसकी ट्रांज़िटिव डिपेंडेंसी के लिए, --cpu को x86 में बदल देता है. tool1 पर मौजूद select, tool1 के बिल्ड पैरामीटर का इस्तेमाल करता है. इनमें --cpu=x86 शामिल है.

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

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

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

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

बिल्ट-इन फ़्लैग

--cpu जैसे फ़्लैग, Bazel में बिल्ट-इन होते हैं. बिल्ड टूल, सभी प्रोजेक्ट में सभी बिल्ड के लिए, इन्हें स्वाभाविक तौर पर समझता है. इन्हें config_setting's 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's 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()'s 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 का सबसेट शामिल होता है. इससे, select() एक्सप्रेशन में वे config_setting मैच हो पाते हैं.

उदाहरण के लिए, 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_setting की ज़रूरत नहीं पड़ती.

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

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 को तब मैच करना है, जब एक से ज़्यादा शर्तें मैच करती हैं, तो AND चेनिंग का इस्तेमाल करें.

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 मैक्रो, select के अंदर सीधे तौर पर OR करने की शर्तों के साथ काम करता है:

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 के `selects` मॉड्यूल में मौजूद config_setting_group मैक्रो, कई config_setting को OR करने के साथ काम करता है:selects

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's लोडिंग फ़ेज़ में काम करता है. इसका मतलब है कि इसे यह नहीं पता होता कि कोई टारगेट, कमांड लाइन के किन फ़्लैग का इस्तेमाल करता है. ऐसा इसलिए है, क्योंकि इन फ़्लैग का आकलन, बिल्ड के बाद के फ़ेज़ (विश्लेषण के फ़े1ज़) में किया जाता है. इसलिए, यह तय नहीं किया जा सकता कि select() की कौनसी ब्रांच चुनी गई हैं.

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

इसके लिए, इन्हें आज़माएं:

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

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

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

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

# 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({
        "//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.

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

क्या मैं select() को डिक्शनरी की तरह पढ़ सकता/सकती हूं?

मैक्रो select(s) का आकलन नहीं कर सकते, क्योंकि मैक्रो का आकलन, Bazel के बिल्ड के कमांड लाइन पैरामीटर के बारे में जानने से पहले किया जाता है. क्या वे कम से कम 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 = {
        "//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

अगर आपको 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 + "> $@",
    )

bind() के साथ select() क्यों काम नहीं करता?

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

तकनीकी तौर पर, bind(), रेपो नियम है, न कि BUILD नियम.

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

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

ज़रूरत पड़ने पर, bind() टारगेट को 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 करना ही है, तो क्या किया जा सकता है?

अगर आपके बिल्ड की ज़रूरी शर्तों के लिए, प्लैटफ़ॉर्म की जांच करना ज़रूरी है, तो config_setting में --platforms फ़्लैग की वैल्यू को फ़्लिप किया जा सकता है:

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 टीम, ऐसा करने की सलाह नहीं देती. इससे आपके बिल्ड पर ज़रूरत से ज़्यादा पाबंदियां लग जाती हैं. साथ ही, जब अनुमानित शर्त मैच नहीं करती, तो उपयोगकर्ता भ्रमित हो जाते हैं.