कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट को आम तौर पर 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
का उचित अधिसमुच्चय होता है. उदाहरण के लिए, values = {"cpu": "x86", "compilation_mode": "dbg"}
, values = {"cpu": "x86"}
का एक ऐसा खास वर्शन है जिसमें कोई अस्पष्टता नहीं है.
अगर कोई भी शर्त पूरी नहीं होती है, तो //conditions:default
वाली बिल्ट-इन शर्त अपने-आप पूरी हो जाती है.
इस उदाहरण में deps
का इस्तेमाल किया गया है. हालांकि, select()
का इस्तेमाल srcs
, resources
, cmd
, और ज़्यादातर अन्य एट्रिब्यूट के साथ भी किया जा सकता है. सिर्फ़ कुछ एट्रिब्यूट कॉन्फ़िगर नहीं किए जा सकते. इनके बारे में साफ़ तौर पर बताया गया है. उदाहरण के लिए,
config_setting
का values
एट्रिब्यूट कॉन्फ़िगर नहीं किया जा सकता.
select()
और डिपेंडेंसी
कुछ एट्रिब्यूट, टारगेट के तहत आने वाली सभी ट्रांज़िटिव डिपेंडेंसी के लिए, बिल्ड पैरामीटर बदलते हैं. उदाहरण के लिए, genrule
का tools
, Bazel चलाने वाली मशीन के सीपीयू के --cpu
में बदल जाता है. क्रॉस-कंपाइलेशन की वजह से, यह सीपीयू उस सीपीयू से अलग हो सकता है जिसके लिए टारगेट बनाया गया है. इसे कॉन्फ़िगरेशन ट्रांज़िशन कहा जाता है.
दिया गया
#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
हो जाती है. select
on
tool1
, tool1
के बिल्ड पैरामीटर का इस्तेमाल करता है. इनमें --cpu=x86
शामिल है.
कॉन्फ़िगरेशन की शर्तें
कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट में मौजूद हर कुंजी, config_setting
या constraint_value
का लेबल रेफ़रंस होती है.
config_setting
, कमांड लाइन फ़्लैग की
अनुमानित सेटिंग का सिर्फ़ एक कलेक्शन है. इन्हें टारगेट में शामिल करने से, "स्टैंडर्ड" शर्तों को बनाए रखना आसान हो जाता है. उपयोगकर्ता इन्हें कई जगहों से देख सकते हैं.
constraint_value
मल्टी-प्लैटफ़ॉर्म बिहेवियर के साथ काम करता है.
पहले से मौजूद फ़्लैग
--cpu
जैसे फ़्लैग, Bazel में पहले से मौजूद होते हैं. Bazel, बिल्ड टूल के तौर पर सभी प्रोजेक्ट के सभी बिल्ड के लिए, इन्हें आसानी से समझ लेता है. इन्हें config_setting
के values
एट्रिब्यूट के साथ तय किया जाता है:
config_setting(
name = "meaningful_condition_name",
values = {
"flag1": "value1",
"flag2": "value2",
...
},
)
flagN
फ़्लैग का नाम है (--
के बिना, इसलिए "--cpu"
के बजाय "cpu"
). valueN
उस फ़्लैग के लिए अनुमानित वैल्यू है. :meaningful_condition_name
तब मैच करता है, जब values
में मौजूद हर एंट्री मैच करती हो. क्रम मायने नहीं रखता.
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_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
करने की सुविधा देता है:
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()
को सटीक तरीके से हल कर सकता है.
आकलन करते समय इन बातों का ध्यान रखें:
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() does work in rules! ज़्यादा जानकारी के लिए, नियमों के साथ काम करने की सुविधा देखें.
इस सवाल का मुख्य मतलब यह है कि 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.
इसमें बदलाव नहीं किया जा सकता, क्योंकि परिभाषा के हिसाब से मैक्रो का आकलन, Bazel के बिल्ड के कमांड-लाइन फ़्लैग को पढ़ने से पहले किया जाता है. इसका मतलब है कि select()s का आकलन करने के लिए, ज़रूरत के मुताबिक जानकारी उपलब्ध नहीं है.
हालांकि, मैक्रो नियमों को ओपेक ब्लोब के तौर पर 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({
"//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()
ऑब्जेक्ट का आकलन कर रहे हैं. 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 = {
"//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 + "> $@",
)
bind() के साथ select() काम क्यों नहीं करता है?
सबसे पहले, bind()
का इस्तेमाल न करें. अब alias()
का इस्तेमाल किया जाता है.
तकनीकी तौर पर, इसका जवाब यह है कि bind()
एक repo
rule है, न कि BUILD rule.
Repo के नियमों का कोई खास कॉन्फ़िगरेशन नहीं होता. साथ ही, इनका आकलन BUILD के नियमों की तरह नहीं किया जाता. इसलिए, bind()
में मौजूद select()
, किसी खास ब्रांच का आकलन नहीं कर सकता.
इसके बजाय, आपको इस तरह के रन-टाइम का पता लगाने के लिए, alias()
का इस्तेमाल करना चाहिए. साथ ही, actual
एट्रिब्यूट में select()
का इस्तेमाल करना चाहिए. यह सही तरीके से काम करता है, क्योंकि alias()
एक BUILD नियम है और इसका आकलन किसी खास कॉन्फ़िगरेशन के साथ किया जाता है.
$ 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
के अलावा कोई और :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 टीम, इस तरीके को इस्तेमाल करने का सुझाव नहीं देती. इससे आपकी बिल्ड प्रोसेस पर काफ़ी असर पड़ता है. साथ ही, जब उम्मीद के मुताबिक शर्त पूरी नहीं होती, तो उपयोगकर्ताओं को भ्रम होता है.