नियम, कार्रवाइयों की एक सीरीज़ तय करता है. Bazel, इन कार्रवाइयों को इनपुट पर लागू करके आउटपुट का एक सेट तैयार करता है. इस सेट को नियम के लागू करने वाले फ़ंक्शन से मिले प्रोवाइडर में रेफ़रंस किया जाता है. उदाहरण के लिए, C++ बाइनरी नियम यह काम कर सकता है:
.cpp
सोर्स फ़ाइलों (इनपुट) का एक सेट लें.- सोर्स फ़ाइलों पर
g++
चलाएं (कार्रवाई). DefaultInfo
provider को एक्ज़ीक्यूटेबल आउटपुट और अन्य फ़ाइलों के साथ वापस भेजें, ताकि रनटाइम के दौरान उन्हें उपलब्ध कराया जा सके.- टारगेट और उसकी डिपेंडेंसी से इकट्ठा की गई C++ से जुड़ी जानकारी के साथ,
CcInfo
प्रोवाइडर को दिखाता है.
Bazel के हिसाब से, g++
और स्टैंडर्ड C++ लाइब्रेरी भी इस नियम के इनपुट हैं. नियम लिखने वाले व्यक्ति के तौर पर, आपको न सिर्फ़ उपयोगकर्ता के दिए गए इनपुट पर ध्यान देना चाहिए, बल्कि उन सभी टूल और लाइब्रेरी पर भी ध्यान देना चाहिए जिनकी ज़रूरत कार्रवाइयों को पूरा करने के लिए होती है.
कोई भी नियम बनाने या उसमें बदलाव करने से पहले, पक्का करें कि आपको Bazel के बिल्ड फ़ेज़ के बारे में पता हो. बिल्ड के तीन चरणों (लोडिंग, विश्लेषण, और एक्ज़ीक्यूशन) को समझना ज़रूरी है. नियमों और मैक्रो के बीच के अंतर को समझने के लिए, मैक्रो के बारे में जानना भी ज़रूरी है. शुरू करने के लिए, सबसे पहले नियमों का ट्यूटोरियल देखें. इसके बाद, इस पेज का इस्तेमाल रेफ़रंस के तौर पर करें.
कुछ नियम, Bazel में पहले से मौजूद होते हैं. ये नेटिव नियम, जैसे कि cc_library
और java_binary
, कुछ भाषाओं के लिए बुनियादी सहायता उपलब्ध कराते हैं.
अपने नियम तय करके, उन भाषाओं और टूल के लिए भी इसी तरह की सुविधाएँ जोड़ी जा सकती हैं
जिनके लिए Bazel नेटिव तौर पर काम नहीं करता.
Bazel, Starlark भाषा का इस्तेमाल करके नियम लिखने के लिए, एक्सटेंसिबिलिटी मॉडल उपलब्ध कराता है. ये नियम .bzl
फ़ाइलों में लिखे जाते हैं. इन्हें सीधे BUILD
फ़ाइलों से लोड किया जा सकता है.
अपना नियम तय करते समय, यह तय किया जा सकता है कि यह किन एट्रिब्यूट के साथ काम करेगा और इसके आउटपुट कैसे जनरेट होंगे.
नियम का implementation
फ़ंक्शन, विश्लेषण के फ़ेज़ के दौरान इसके सटीक व्यवहार को तय करता है. यह फ़ंक्शन, कोई बाहरी कमांड नहीं चलाता. इसके बजाय, यह कार्रवाइयों को रजिस्टर करता है. इनका इस्तेमाल, नियम के आउटपुट बनाने के लिए एक्ज़ीक्यूशन फ़ेज़ के दौरान किया जाएगा. हालांकि, ऐसा सिर्फ़ तब किया जाएगा, जब इनकी ज़रूरत होगी.
नियम बनाना
.bzl
फ़ाइल में, rule फ़ंक्शन का इस्तेमाल करके नया नियम तय करें. इसके बाद, नतीजे को ग्लोबल वैरिएबल में सेव करें. rule
को कॉल करने पर, एट्रिब्यूट और लागू करने का फ़ंक्शन तय किया जाता है:
example_library = rule(
implementation = _example_library_impl,
attrs = {
"deps": attr.label_list(),
...
},
)
इससे example_library
नाम के नियम के टाइप के बारे में पता चलता है.
rule
को कॉल करते समय, यह भी बताना होगा कि नियम, executable=True
के साथ एक्ज़ीक्यूटेबल आउटपुट बनाता है या test=True
के साथ खास तौर पर टेस्ट एक्ज़ीक्यूटेबल बनाता है. अगर नियम, टेस्ट एक्ज़ीक्यूटेबल बनाता है, तो यह टेस्ट नियम होता है. साथ ही, नियम का नाम _test
पर खत्म होना चाहिए.
टारगेट इंस्टैंटिएशन
नियमों को BUILD
फ़ाइलों में लोड और कॉल किया जा सकता है:
load('//some/pkg:rules.bzl', 'example_library')
example_library(
name = "example_target",
deps = [":another_target"],
...
)
बिल्ड के नियम को कॉल करने पर कोई वैल्यू नहीं मिलती. हालांकि, इससे टारगेट तय करने का साइड इफ़ेक्ट होता है. इसे नियम को लागू करना कहा जाता है. इससे नए टारगेट का नाम और टारगेट के एट्रिब्यूट की वैल्यू तय की जाती हैं.
नियमों को Starlark फ़ंक्शन से भी कॉल किया जा सकता है. साथ ही, इन्हें .bzl
फ़ाइलों में लोड किया जा सकता है.
नियमों को कॉल करने वाले Starlark फ़ंक्शन को Starlark मैक्रो कहा जाता है.
Starlark मैक्रो को आखिर में BUILD
फ़ाइलों से कॉल किया जाना चाहिए. इन्हें सिर्फ़ लोडिंग फ़ेज़ के दौरान कॉल किया जा सकता है. ऐसा तब होता है, जब टारगेट को इंस्टैंटिएट करने के लिए BUILD
फ़ाइलों का आकलन किया जाता है.
विशेषताएं
एट्रिब्यूट, नियम का एक तर्क होता है. एट्रिब्यूट, टारगेट के लागू करने के लिए खास वैल्यू दे सकते हैं. इसके अलावा, वे अन्य टारगेट का रेफ़रंस दे सकते हैं. इससे डिपेंडेंसी का ग्राफ़ बनता है.
नियम के हिसाब से एट्रिब्यूट, जैसे कि srcs
या deps
, rule
के attrs
पैरामीटर को एट्रिब्यूट के नामों से लेकर स्कीमा (attr
मॉड्यूल का इस्तेमाल करके बनाया गया) तक का मैप पास करके तय किए जाते हैं.
सामान्य एट्रिब्यूट, जैसे कि
name
और visibility
, सभी नियमों में अपने-आप जुड़ जाते हैं. अतिरिक्त एट्रिब्यूट, खास तौर पर एक्ज़ीक्यूटेबल और टेस्ट नियमों में अपने-आप जुड़ जाते हैं. जिन एट्रिब्यूट को किसी नियम में अपने-आप जोड़ दिया जाता है उन्हें attrs
को पास की गई डिक्शनरी में शामिल नहीं किया जा सकता.
निर्भरता वाले एट्रिब्यूट
सोर्स कोड को प्रोसेस करने वाले नियम, आम तौर पर अलग-अलग तरह की डिपेंडेंसी को मैनेज करने के लिए, इन एट्रिब्यूट को तय करते हैं:
srcs
टारगेट की कार्रवाइयों से प्रोसेस की गई सोर्स फ़ाइलों के बारे में बताता है. अक्सर, एट्रिब्यूट स्कीमा से पता चलता है कि नियम जिस तरह की सोर्स फ़ाइल को प्रोसेस करता है उसके लिए किस तरह के फ़ाइल एक्सटेंशन ज़रूरी हैं. हेडर फ़ाइलों वाली भाषाओं के लिए बने नियमों में, आम तौर पर टारगेट और उसके उपभोक्ताओं के ज़रिए प्रोसेस किए गए हेडर के लिए, अलगhdrs
एट्रिब्यूट तय किया जाता है.deps
किसी टारगेट के लिए कोड डिपेंडेंसी के बारे में बताता है. एट्रिब्यूट स्कीमा में यह जानकारी होनी चाहिए कि उन डिपेंडेंसी को किन प्रोवाइडर को उपलब्ध कराना होगा. (उदाहरण के लिए,cc_library
CcInfo
उपलब्ध कराता है.)data
से उन फ़ाइलों के बारे में पता चलता है जिन्हें रनटाइम के दौरान, किसी भी ऐसे एक्ज़ीक्यूटेबल के लिए उपलब्ध कराया जाना है जो किसी टारगेट पर निर्भर करता है. इससे किसी भी फ़ाइल को तय किया जा सकता है.
example_library = rule(
implementation = _example_library_impl,
attrs = {
"srcs": attr.label_list(allow_files = [".example"]),
"hdrs": attr.label_list(allow_files = [".header"]),
"deps": attr.label_list(providers = [ExampleInfo]),
"data": attr.label_list(allow_files = True),
...
},
)
ये डिपेंडेंसी एट्रिब्यूट के उदाहरण हैं. ऐसा कोई भी एट्रिब्यूट जो इनपुट लेबल (attr.label_list
, attr.label
या attr.label_keyed_string_dict
के साथ तय किए गए लेबल) के बारे में बताता है, वह किसी टारगेट और उन टारगेट के बीच किसी खास तरह की डिपेंडेंसी के बारे में बताता है जिनके लेबल (या उनसे जुड़े Label
ऑब्जेक्ट) टारगेट को तय करते समय उस एट्रिब्यूट में शामिल किए जाते हैं. इन लेबल के लिए रिपॉज़िटरी और पाथ, तय किए गए टारगेट के हिसाब से तय किया जाता है.
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
इस उदाहरण में, other_target
, my_target
पर निर्भर है. इसलिए, other_target
का विश्लेषण पहले किया जाता है. अगर टारगेट के डिपेंडेंसी ग्राफ़ में कोई साइकल है, तो यह एक गड़बड़ी है.
निजी एट्रिब्यूट और इंप्लिसिट डिपेंडेंसी
डिफ़ॉल्ट वैल्यू वाले डिपेंडेंसी एट्रिब्यूट से, डिपेंडेंसी का साफ़ तौर पर पता नहीं चलता है. यह जानकारी इसलिए शामिल की गई है, क्योंकि यह टारगेट ग्राफ़ का हिस्सा है. हालांकि, उपयोगकर्ता ने इसे BUILD
फ़ाइल में शामिल नहीं किया है. इम्प्लिसिट डिपेंडेंसी, किसी नियम और टूल (बिल्ड-टाइम डिपेंडेंसी, जैसे कि कंपाइलर) के बीच संबंध को हार्ड-कोड करने के लिए फ़ायदेमंद होती हैं. ऐसा इसलिए, क्योंकि ज़्यादातर समय उपयोगकर्ता को यह बताने में दिलचस्पी नहीं होती कि नियम किस टूल का इस्तेमाल करता है. नियम को लागू करने वाले फ़ंक्शन में, इसे अन्य डिपेंडेंसी की तरह ही माना जाता है.
अगर आपको किसी वैल्यू को बदलने की अनुमति दिए बिना, उस पर लागू होने वाली डिपेंडेंसी देनी है, तो एट्रिब्यूट को निजी के तौर पर सेट किया जा सकता है. इसके लिए, एट्रिब्यूट का नाम अंडरस्कोर (_
) से शुरू होना चाहिए. निजी एट्रिब्यूट के लिए डिफ़ॉल्ट वैल्यू होना ज़रूरी है. आम तौर पर, प्राइवेट एट्रिब्यूट का इस्तेमाल सिर्फ़ उन डिपेंडेंसी के लिए किया जाता है जो साफ़ तौर पर नहीं बताई जाती हैं.
example_library = rule(
implementation = _example_library_impl,
attrs = {
...
"_compiler": attr.label(
default = Label("//tools:example_compiler"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
)
इस उदाहरण में, example_library
टाइप के हर टारगेट की, कंपाइलर //tools:example_compiler
पर इंप्लिसिट डिपेंडेंसी होती है. इससे example_library
के लागू करने वाले फ़ंक्शन को ऐसी कार्रवाइयां जनरेट करने की अनुमति मिलती है जो कंपाइलर को शुरू करती हैं. भले ही, उपयोगकर्ता ने इसके लेबल को इनपुट के तौर पर पास न किया हो. _compiler
एक निजी एट्रिब्यूट है. इसलिए, ctx.attr._compiler
इस नियम के सभी टारगेट में हमेशा //tools:example_compiler
पर पॉइंट करेगा. इसके अलावा, एट्रिब्यूट का नाम compiler
रखा जा सकता है. इसमें अंडरस्कोर नहीं होगा और डिफ़ॉल्ट वैल्यू बनी रहेगी. इससे उपयोगकर्ताओं को ज़रूरत पड़ने पर किसी दूसरे कंपाइलर का इस्तेमाल करने की अनुमति मिलती है. हालांकि, इसके लिए कंपाइलर के लेबल के बारे में जानकारी होना ज़रूरी नहीं है.
आम तौर पर, इंप्लिसिट डिपेंडेंसी का इस्तेमाल उन टूल के लिए किया जाता है जो नियम लागू करने वाली फ़ाइल के साथ एक ही रिपॉज़िटरी में मौजूद होते हैं. अगर टूल, एक्ज़ीक्यूशन प्लैटफ़ॉर्म या किसी दूसरी रिपॉज़िटरी से आता है, तो नियम को उस टूल को टूलचेन से पाना चाहिए.
आउटपुट एट्रिब्यूट
आउटपुट एट्रिब्यूट, जैसे कि attr.output
और attr.output_list
, एक ऐसी आउटपुट फ़ाइल का एलान करते हैं जिसे टारगेट जनरेट करता है. ये डिपेंडेंसी एट्रिब्यूट से दो तरह से अलग होते हैं:
- ये, आउटपुट फ़ाइल के टारगेट तय करते हैं. इसके बजाय, इन्हें किसी दूसरी जगह पर तय किए गए टारगेट का इस्तेमाल करना चाहिए.
- आउटपुट फ़ाइल के टारगेट, इंस्टैंटिएट किए गए नियम के टारगेट पर निर्भर करते हैं.
आम तौर पर, आउटपुट एट्रिब्यूट का इस्तेमाल सिर्फ़ तब किया जाता है, जब किसी नियम को उपयोगकर्ता के तय किए गए नामों वाले आउटपुट बनाने होते हैं. ये नाम, टारगेट के नाम पर आधारित नहीं हो सकते. अगर किसी नियम में एक आउटपुट एट्रिब्यूट है, तो आम तौर पर उसका नाम out
या outs
होता है.
आउटपुट एट्रिब्यूट, पहले से तय किए गए आउटपुट बनाने का सबसे सही तरीका है. इन पर खास तौर पर भरोसा किया जा सकता है या कमांड लाइन पर अनुरोध किया जा सकता है.
लागू करने से जुड़ा फ़ंक्शन
हर नियम के लिए, implementation
फ़ंक्शन ज़रूरी होता है. इन फ़ंक्शन को सिर्फ़ विश्लेषण के चरण में लागू किया जाता है. साथ ही, ये फ़ंक्शन, लोडिंग के चरण में जनरेट किए गए टारगेट के ग्राफ़ को, कार्रवाइयों के ग्राफ़ में बदलते हैं. इन कार्रवाइयों को लागू करने के चरण में पूरा किया जाता है. इसलिए, इंप्लिमेंटेशन फ़ंक्शन, फ़ाइलों को न तो पढ़ सकते हैं और न ही उनमें बदलाव कर सकते हैं.
नियम लागू करने वाले फ़ंक्शन आम तौर पर निजी होते हैं. इनका नाम अंडरस्कोर से शुरू होता है. आम तौर पर, इनका नाम इनके नियम के नाम जैसा ही होता है. हालांकि, इनके नाम के आखिर में _impl
जोड़ा जाता है.
लागू करने वाले फ़ंक्शन में सिर्फ़ एक पैरामीटर होता है: नियम कॉन्टेक्स्ट, जिसे आम तौर पर ctx
कहा जाता है. ये सेवा देने वाली कंपनियों की सूची दिखाते हैं.
टारगेट
डिपेंडेंसी को विश्लेषण के समय Target
ऑब्जेक्ट के तौर पर दिखाया जाता है. इन ऑब्जेक्ट में, टारगेट के इंप्लीमेंटेशन फ़ंक्शन को एक्ज़ीक्यूट करने पर जनरेट किए गए providers होते हैं.
ctx.attr
में हर डिपेंडेंसी एट्रिब्यूट के नाम से जुड़े फ़ील्ड होते हैं. इनमें Target
ऑब्जेक्ट होते हैं, जो उस एट्रिब्यूट के ज़रिए हर डायरेक्ट डिपेंडेंसी को दिखाते हैं. label_list
एट्रिब्यूट के लिए, यह Targets
की सूची है. label
एट्रिब्यूट के लिए, यह एक Target
या None
होता है.
टारगेट के लागू करने वाले फ़ंक्शन से, सेवा देने वाली कंपनियों के ऑब्जेक्ट की सूची मिलती है:
return [ExampleInfo(headers = depset(...))]
इन्हें इंडेक्स नोटेशन ([]
) का इस्तेमाल करके ऐक्सेस किया जा सकता है. इसमें, प्रोवाइडर का टाइप एक कुंजी के तौर पर होता है. ये Starlark में तय किए गए कस्टम प्रोवाइडर या Starlark ग्लोबल वैरिएबल के तौर पर उपलब्ध नेटिव नियमों के लिए प्रोवाइडर हो सकते हैं.
उदाहरण के लिए, अगर कोई नियम hdrs
एट्रिब्यूट के ज़रिए हेडर फ़ाइलें लेता है और उन्हें टारगेट और उसके उपभोक्ताओं की कंपाइलेशन कार्रवाइयों को उपलब्ध कराता है, तो वह उन्हें इस तरह इकट्ठा कर सकता है:
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
लेगसी स्टाइल में, प्रोवाइडर ऑब्जेक्ट की सूची के बजाय टारगेट के लागू करने वाले फ़ंक्शन से struct
दिखाया जाता है:
return struct(example_info = struct(headers = depset(...)))
Target
ऑब्जेक्ट के संबंधित फ़ील्ड से, सेवा देने वाली कंपनियों की जानकारी वापस पाई जा सकती है:
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
इस स्टाइल का इस्तेमाल करने से बचना चाहिए. साथ ही, नियमों को इससे हटा देना चाहिए.
फ़ाइलें
फ़ाइलों को File
ऑब्जेक्ट के तौर पर दिखाया जाता है. Bazel, विश्लेषण के दौरान फ़ाइल I/O नहीं करता. इसलिए, इन ऑब्जेक्ट का इस्तेमाल फ़ाइल के कॉन्टेंट को सीधे तौर पर पढ़ने या लिखने के लिए नहीं किया जा सकता. इसके बजाय, इन्हें ऐक्शन-एमिटिंग फ़ंक्शन (ctx.actions
देखें) को पास किया जाता है, ताकि ऐक्शन ग्राफ़ के हिस्से बनाए जा सकें.
File
, सोर्स फ़ाइल या जनरेट की गई फ़ाइल हो सकती है. जनरेट की गई हर फ़ाइल, सिर्फ़ एक कार्रवाई का आउटपुट होनी चाहिए. सोर्स फ़ाइलें, किसी कार्रवाई का आउटपुट नहीं हो सकतीं.
हर डिपेंडेंसी एट्रिब्यूट के लिए, ctx.files
के संबंधित फ़ील्ड में, उस एट्रिब्यूट के ज़रिए सभी डिपेंडेंसी के डिफ़ॉल्ट आउटपुट की सूची होती है:
def _example_library_impl(ctx):
...
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
...
ctx.file
में, निर्भरता वाले एट्रिब्यूट के लिए एक File
या None
शामिल है. इन एट्रिब्यूट की खास जानकारी में allow_single_file=True
सेट होता है.
ctx.executable
, ctx.file
की तरह ही काम करता है. हालांकि, इसमें सिर्फ़ उन डिपेंडेंसी एट्रिब्यूट के फ़ील्ड शामिल होते हैं जिनके स्पेसिफ़िकेशन में executable=True
सेट होता है.
आउटपुट तय करना
विश्लेषण के फ़ेज़ के दौरान, नियम को लागू करने वाला फ़ंक्शन आउटपुट बना सकता है.
लोडिंग के दौरान सभी लेबल की जानकारी होना ज़रूरी है. इसलिए, इन अतिरिक्त आउटपुट में कोई लेबल नहीं है. आउटपुट के लिए File
ऑब्जेक्ट, ctx.actions.declare_file
और ctx.actions.declare_directory
का इस्तेमाल करके बनाए जा सकते हैं. अक्सर, आउटपुट के नाम टारगेट के नाम पर आधारित होते हैं,
ctx.label.name
:
def _example_library_impl(ctx):
...
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
...
पहले से तय किए गए आउटपुट के लिए, जैसे कि आउटपुट एट्रिब्यूट के लिए बनाए गए आउटपुट, File
ऑब्जेक्ट को ctx.outputs
के संबंधित फ़ील्ड से वापस पाया जा सकता है.
कार्रवाइयां
कार्रवाई से पता चलता है कि इनपुट के सेट से आउटपुट का सेट कैसे जनरेट किया जाता है. उदाहरण के लिए, "hello.c पर gcc चलाएं और hello.o पाएं". कोई कार्रवाई बनाने पर, Bazel तुरंत कमांड नहीं चलाता है. यह इसे डिपेंडेंसी के ग्राफ़ में रजिस्टर करता है, क्योंकि कोई कार्रवाई किसी दूसरी कार्रवाई के आउटपुट पर निर्भर हो सकती है. उदाहरण के लिए, C में कंपाइलर के बाद लिंकर को कॉल किया जाना चाहिए.
कार्रवाइयां करने वाले सामान्य फ़ंक्शन, ctx.actions
में तय किए जाते हैं:
- एक्ज़ीक्यूटेबल चलाने के लिए,
ctx.actions.run
. - शेल कमांड चलाने के लिए,
ctx.actions.run_shell
का इस्तेमाल करें. ctx.actions.write
का इस्तेमाल किया जाता है.ctx.actions.expand_template
पर क्लिक करें, ताकि टेंप्लेट से फ़ाइल जनरेट की जा सके.
ctx.actions.args
का इस्तेमाल, कार्रवाइयों के लिए आर्ग्युमेंट को आसानी से इकट्ठा करने के लिए किया जा सकता है. यह एक्ज़ीक्यूशन टाइम तक depsets को फ़्लैट करने से बचाता है:
def _example_library_impl(ctx):
...
transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
inputs = depset(srcs, transitive=[headers])
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
args = ctx.actions.args()
args.add_joined("-h", headers, join_with=",")
args.add_joined("-s", srcs, join_with=",")
args.add("-o", output_file)
ctx.actions.run(
mnemonic = "ExampleCompile",
executable = ctx.executable._compiler,
arguments = [args],
inputs = inputs,
outputs = [output_file],
)
...
कार्रवाइयों में, इनपुट फ़ाइलों की सूची या डेपसेट का इस्तेमाल किया जाता है. साथ ही, आउटपुट फ़ाइलों की (खाली नहीं) सूची जनरेट की जाती है. विश्लेषण के चरण के दौरान, इनपुट और आउटपुट फ़ाइलों के सेट के बारे में पता होना चाहिए. यह एट्रिब्यूट की वैल्यू पर निर्भर कर सकता है. इसमें डिपेंडेंसी से जुड़े प्रोवाइडर भी शामिल हैं. हालांकि, यह एक्ज़ीक्यूशन के नतीजे पर निर्भर नहीं कर सकता. उदाहरण के लिए, अगर आपकी कार्रवाई में अनज़िप कमांड का इस्तेमाल किया जाता है, तो आपको यह बताना होगा कि आपको किन फ़ाइलों को अनज़िप करना है. ऐसा अनज़िप कमांड चलाने से पहले करना होगा. जिन कार्रवाइयों से अंदरूनी तौर पर कई फ़ाइलें बनती हैं उन्हें एक ही फ़ाइल (जैसे कि zip, tar या अन्य संग्रह फ़ॉर्मैट) में रैप किया जा सकता है.
कार्रवाइयों में, सभी इनपुट की सूची होनी चाहिए. ऐसे इनपुट इस्तेमाल करने की अनुमति है जिनका इस्तेमाल नहीं किया जाता. हालांकि, यह तरीका असरदार नहीं है.
कार्रवाइयों को अपने सभी आउटपुट जनरेट करने चाहिए. वे अन्य फ़ाइलें लिख सकते हैं, लेकिन आउटपुट में मौजूद नहीं होने वाली कोई भी चीज़ उपभोक्ताओं के लिए उपलब्ध नहीं होगी. तय किए गए सभी आउटपुट, किसी ऐक्शन के ज़रिए लिखे जाने चाहिए.
ऐक्शन की तुलना प्योर फ़ंक्शन से की जा सकती है: ये सिर्फ़ दिए गए इनपुट पर निर्भर होने चाहिए. साथ ही, इन्हें कंप्यूटर की जानकारी, उपयोगकर्ता नाम, घड़ी, नेटवर्क या I/O डिवाइसों को ऐक्सेस करने से बचना चाहिए. हालांकि, इनपुट पढ़ने और आउटपुट लिखने के लिए इन्हें ऐक्सेस किया जा सकता है. यह ज़रूरी है, क्योंकि आउटपुट को कैश मेमोरी में सेव किया जाएगा और फिर से इस्तेमाल किया जाएगा.
डिपेंडेंसी को Bazel हल करता है. इससे यह तय होता है कि कौनसी कार्रवाइयां की जाएंगी. अगर डिपेंडेंसी ग्राफ़ में कोई साइकल है, तो यह एक गड़बड़ी है. कार्रवाई बनाने से इस बात की गारंटी नहीं मिलती कि उसे लागू किया जाएगा. यह इस बात पर निर्भर करता है कि बिल्ड के लिए, इसके आउटपुट की ज़रूरत है या नहीं.
सेवा देने वाली कंपनियां
प्रोवाइडर, जानकारी के ऐसे हिस्से होते हैं जिन्हें कोई नियम, उन अन्य नियमों के लिए उपलब्ध कराता है जो उस पर निर्भर होते हैं. इस डेटा में आउटपुट फ़ाइलें, लाइब्रेरी, टूल की कमांड लाइन पर पास किए जाने वाले पैरामीटर या टारगेट के उपभोक्ताओं को पता होनी चाहिए, ऐसी कोई भी जानकारी शामिल हो सकती है.
किसी नियम को लागू करने वाला फ़ंक्शन, सिर्फ़ इंस्टैंटिएट किए गए टारगेट की इमीडिएट डिपेंडेंसी से प्रोवाइडर पढ़ सकता है. इसलिए, नियमों को टारगेट की डिपेंडेंसी से ऐसी कोई भी जानकारी फ़ॉरवर्ड करनी होती है जिसे टारगेट के उपभोक्ताओं को जानना ज़रूरी है. आम तौर पर, ऐसा depset
में जानकारी इकट्ठा करके किया जाता है.
किसी टारगेट के प्रोवाइडर, Provider
ऑब्जेक्ट की एक सूची से तय होते हैं. यह सूची, लागू करने वाले फ़ंक्शन से मिलती है.
लागू करने वाले पुराने फ़ंक्शन को लेगसी स्टाइल में भी लिखा जा सकता है. इसमें लागू करने वाला फ़ंक्शन, प्रोवाइडर ऑब्जेक्ट की सूची के बजाय struct
दिखाता है. इस स्टाइल का इस्तेमाल करने से बचना चाहिए. साथ ही, नियमों को इससे हटा देना चाहिए.
डिफ़ॉल्ट आउटपुट
किसी टारगेट के डिफ़ॉल्ट आउटपुट वे आउटपुट होते हैं जिनके लिए डिफ़ॉल्ट रूप से अनुरोध किया जाता है. ऐसा तब होता है, जब कमांड लाइन पर टारगेट को बिल्ड करने का अनुरोध किया जाता है. उदाहरण के लिए, java_library
टारगेट //pkg:foo
का डिफ़ॉल्ट आउटपुट foo.jar
है. इसलिए, इसे bazel build //pkg:foo
कमांड से बनाया जाएगा.
डिफ़ॉल्ट आउटपुट, DefaultInfo
के files
पैरामीटर से तय किए जाते हैं:
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
अगर किसी नियम को लागू करने पर DefaultInfo
नहीं मिलता है या files
पैरामीटर नहीं दिया गया है, तो DefaultInfo.files
डिफ़ॉल्ट रूप से सभी पहले से तय किए गए आउटपुट पर सेट हो जाता है. आम तौर पर, ये output एट्रिब्यूट से बनाए जाते हैं.
कार्रवाइयां करने वाले नियमों को डिफ़ॉल्ट आउटपुट देने चाहिए. भले ही, उन आउटपुट का सीधे तौर पर इस्तेमाल न किया जाना हो. अनुरोध किए गए आउटपुट के ग्राफ़ में मौजूद नहीं होने वाली कार्रवाइयों को हटा दिया जाता है. अगर किसी आउटपुट का इस्तेमाल सिर्फ़ टारगेट के उपभोक्ताओं के लिए किया जाता है, तो टारगेट को अलग से बनाने पर वे कार्रवाइयां नहीं की जाएंगी. इससे डीबग करना ज़्यादा मुश्किल हो जाता है, क्योंकि सिर्फ़ फ़ेल हुए टारगेट को फिर से बनाने से, गड़बड़ी को ठीक नहीं किया जा सकेगा.
Runfiles
रनफ़ाइलें, फ़ाइलों का एक सेट होती हैं. इनका इस्तेमाल, टारगेट के ज़रिए रनटाइम में किया जाता है. इनका इस्तेमाल, बिल्ड टाइम में नहीं किया जाता. एक्ज़ीक्यूशन फ़ेज़ के दौरान, Bazel एक डायरेक्ट्री ट्री बनाता है. इसमें रनफ़ाइल की ओर इशारा करने वाले सिमलंक होते हैं. इससे बाइनरी के लिए एनवायरमेंट तैयार किया जाता है, ताकि वह रनटाइम के दौरान रनफ़ाइलें ऐक्सेस कर सके.
नियम बनाते समय, रनफ़ाइलें मैन्युअल तरीके से जोड़ी जा सकती हैं.
runfiles
ऑब्जेक्ट, नियम के कॉन्टेक्स्ट पर runfiles
तरीके से बनाए जा सकते हैं. साथ ही, इन्हें ctx.runfiles
पर runfiles
पैरामीटर को पास किया जा सकता है.DefaultInfo
एक्ज़ीक्यूटेबल नियमों का एक्ज़ीक्यूटेबल आउटपुट, रनफ़ाइलों में अपने-आप जुड़ जाता है.
कुछ नियमों में ऐसे एट्रिब्यूट तय किए जाते हैं जिन्हें आम तौर पर data
कहा जाता है. इनके आउटपुट, टारगेट की रनफ़ाइल में जोड़े जाते हैं. रनफ़ाइलें, data
के साथ-साथ उन एट्रिब्यूट से भी मर्ज की जानी चाहिए जो आखिर में कोड को एक्ज़ीक्यूट करने के लिए उपलब्ध करा सकते हैं. आम तौर पर, srcs
(जिसमें filegroup
टारगेट और उनसे जुड़े data
शामिल हो सकते हैं) और deps
.
def _example_library_impl(ctx):
...
runfiles = ctx.runfiles(files = ctx.files.data)
transitive_runfiles = []
for runfiles_attr in (
ctx.attr.srcs,
ctx.attr.hdrs,
ctx.attr.deps,
ctx.attr.data,
):
for target in runfiles_attr:
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
runfiles = runfiles.merge_all(transitive_runfiles)
return [
DefaultInfo(..., runfiles = runfiles),
...
]
कस्टम प्रोवाइडर
नियम से जुड़ी जानकारी देने के लिए, provider
फ़ंक्शन का इस्तेमाल करके, प्रोवाइडर तय किए जा सकते हैं:
ExampleInfo = provider(
"Info needed to compile/link Example code.",
fields={
"headers": "depset of header Files from transitive dependencies.",
"files_to_link": "depset of Files from compilation.",
})
इसके बाद, नियम लागू करने वाले फ़ंक्शन, प्रोवाइडर इंस्टेंस बना सकते हैं और उन्हें वापस भेज सकते हैं:
def _example_library_impl(ctx):
...
return [
...
ExampleInfo(
headers = headers,
files_to_link = depset(
[output_file],
transitive = [
dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
],
),
)
]
प्रोवाइडर को पसंद के मुताबिक शुरू करना
कस्टम प्रीप्रोसेसिंग और पुष्टि करने के लॉजिक की मदद से, किसी प्रोवाइडर के इंस्टैंटिएशन को सुरक्षित रखा जा सकता है. इसका इस्तेमाल यह पक्का करने के लिए किया जा सकता है कि सभी प्रोवाइडर इंस्टेंस, कुछ इनवेरिएंट का पालन करें. इसके अलावा, इसका इस्तेमाल उपयोगकर्ताओं को इंस्टेंस पाने के लिए, बेहतर एपीआई देने के लिए भी किया जा सकता है.
इसके लिए, provider
फ़ंक्शन को init
कॉलबैक पास किया जाता है. अगर यह कॉलबैक दिया जाता है, तो provider()
का रिटर्न टाइप बदलकर दो वैल्यू का ट्यूपल हो जाता है: प्रोवाइडर सिंबल, जो init
का इस्तेमाल न करने पर सामान्य रिटर्न वैल्यू होती है, और "रॉ कंस्ट्रक्टर".
इस मामले में, जब सेवा देने वाली कंपनी के सिंबल को कॉल किया जाता है, तो नया इंस्टेंस सीधे तौर पर दिखाने के बजाय, यह init
कॉलबैक को आर्ग्युमेंट फ़ॉरवर्ड करेगा. कॉल बैक की रिटर्न वैल्यू, फ़ील्ड के नामों (स्ट्रिंग) को वैल्यू पर मैप करने वाला डिक्शनरी होना चाहिए. इसका इस्तेमाल, नए इंस्टेंस के फ़ील्ड को शुरू करने के लिए किया जाता है. ध्यान दें कि कॉलबैक में कोई भी हस्ताक्षर हो सकता है. अगर तर्क, हस्ताक्षर से मेल नहीं खाते हैं, तो गड़बड़ी की सूचना दी जाती है. ऐसा तब होता है, जब कॉलबैक को सीधे तौर पर शुरू किया गया हो.
इसके उलट, रॉ कंस्ट्रक्टर init
कॉलबैक को बायपास कर देगा.
यहां दिए गए उदाहरण में, init
का इस्तेमाल करके, इसके आर्ग्युमेंट को पहले से प्रोसेस किया गया है और उनकी पुष्टि की गई है:
# //pkg:exampleinfo.bzl
_core_headers = [...] # private constant representing standard library files
# It's possible to define an init accepting positional arguments, but
# keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
if not files_to_link and not allow_empty_files_to_link:
fail("files_to_link may not be empty")
all_headers = depset(_core_headers, transitive = headers)
return {'files_to_link': files_to_link, 'headers': all_headers}
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init)
export ExampleInfo
इसके बाद, नियम लागू करने के दौरान, सेवा देने वाली कंपनी को इस तरह इंस्टैंटिएट किया जा सकता है:
ExampleInfo(
files_to_link=my_files_to_link, # may not be empty
headers = my_headers, # will automatically include the core headers
)
रॉ कंस्ट्रक्टर का इस्तेमाल, ऐसे वैकल्पिक पब्लिक फ़ैक्ट्री फ़ंक्शन को तय करने के लिए किया जा सकता है जो init
लॉजिक से नहीं गुज़रते. उदाहरण के लिए, exampleinfo.bzl में हम यह तय कर सकते हैं:
def make_barebones_exampleinfo(headers):
"""Returns an ExampleInfo with no files_to_link and only the specified headers."""
return _new_exampleinfo(files_to_link = depset(), headers = all_headers)
आम तौर पर, रॉ कंस्ट्रक्टर को ऐसे वैरिएबल से बाइंड किया जाता है जिसका नाम अंडरस्कोर (ऊपर _new_exampleinfo
) से शुरू होता है. इससे उपयोगकर्ता का कोड इसे लोड नहीं कर पाता और न ही ऐसेट देने वाली कंपनी के इंस्टेंस जनरेट कर पाता है.
init
का इस्तेमाल, उपयोगकर्ता को सीधे तौर पर प्रोवाइडर सिंबल को कॉल करने से रोकने के लिए भी किया जा सकता है. इससे उपयोगकर्ता को फ़ैक्ट्री फ़ंक्शन का इस्तेमाल करने के लिए मजबूर किया जा सकता है:
def _exampleinfo_init_banned(*args, **kwargs):
fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init_banned)
def make_exampleinfo(...):
...
return _new_exampleinfo(...)
लागू किए जा सकने वाले नियम और जांच के नियम
लागू किए जा सकने वाले नियमों में ऐसे टारगेट तय किए जाते हैं जिन्हें bazel run
निर्देश से शुरू किया जा सकता है.
जांच के लिए बनाए गए नियम, लागू किए जा सकने वाले खास तरह के नियम होते हैं. इनके टारगेट को bazel test
निर्देश से भी लागू किया जा सकता है. एक्ज़ीक्यूटेबल और टेस्ट रूल बनाने के लिए, rule
को कॉल करते समय, executable
या test
आर्ग्युमेंट को True
पर सेट करें:
example_binary = rule(
implementation = _example_binary_impl,
executable = True,
...
)
example_test = rule(
implementation = _example_binary_impl,
test = True,
...
)
जांच के नियमों के नाम के आखिर में _test
होना चाहिए. (आम तौर पर, टेस्ट टारगेट के नाम भी _test
पर खत्म होते हैं. हालांकि, ऐसा करना ज़रूरी नहीं है.) टेस्ट नहीं किए गए नियमों में यह प्रत्यय नहीं होना चाहिए.
दोनों तरह के नियमों से, एक्ज़ीक्यूटेबल आउटपुट फ़ाइल जनरेट होनी चाहिए. यह फ़ाइल, पहले से तय की गई हो सकती है या नहीं भी हो सकती है. इसे run
या test
कमांड से शुरू किया जाएगा. Bazel को यह बताने के लिए कि नियम के किन आउटपुट को इस एक्ज़ीक्यूटेबल के तौर पर इस्तेमाल करना है, उन्हें दिखाए गए तरीके से DefaultInfo
प्रोवाइडर के executable
आर्ग्युमेंट के तौर पर पास करें. वह executable
, नियम के डिफ़ॉल्ट आउटपुट में जोड़ा जाता है. इसलिए, आपको उसे executable
और files
, दोनों में पास करने की ज़रूरत नहीं है. इसे runfiles में भी जोड़ा जाता है:
def _example_binary_impl(ctx):
executable = ctx.actions.declare_file(ctx.label.name)
...
return [
DefaultInfo(executable = executable, ...),
...
]
इस फ़ाइल को जनरेट करने वाली कार्रवाई को, फ़ाइल पर एक्ज़ीक्यूटेबल बिट सेट करना होगा. ctx.actions.run
या ctx.actions.run_shell
कार्रवाई के लिए, यह काम उस टूल को करना चाहिए जिसे कार्रवाई के लिए इस्तेमाल किया जाता है. ctx.actions.write
कार्रवाई के लिए, is_executable=True
पास करें.
लेगसी तरीके के तौर पर, लागू किए जा सकने वाले नियमों में ctx.outputs.executable
नाम का एक खास आउटपुट पहले से तय होता है. अगर DefaultInfo
का इस्तेमाल करके कोई डिफ़ॉल्ट एक्ज़ीक्यूटेबल फ़ाइल नहीं दी जाती है, तो इस फ़ाइल का इस्तेमाल डिफ़ॉल्ट एक्ज़ीक्यूटेबल फ़ाइल के तौर पर किया जाता है. इसका इस्तेमाल किसी और काम के लिए नहीं किया जाना चाहिए. इस आउटपुट मैकेनिज़्म का इस्तेमाल अब नहीं किया जा सकता, क्योंकि यह विश्लेषण के दौरान एक्ज़ीक्यूटेबल फ़ाइल के नाम को पसंद के मुताबिक बनाने की सुविधा के साथ काम नहीं करता है.
लागू किए जा सकने वाले नियम और जांच के नियम के उदाहरण देखें.
एक्ज़ीक्यूटेबल नियमों और टेस्ट नियमों में, सभी नियमों के लिए जोड़े गए एट्रिब्यूट के अलावा, कुछ और एट्रिब्यूट भी शामिल होते हैं. डिफ़ॉल्ट रूप से जोड़े गए एट्रिब्यूट की डिफ़ॉल्ट वैल्यू में बदलाव नहीं किया जा सकता. हालांकि, किसी निजी नियम को Starlark मैक्रो में रैप करके, डिफ़ॉल्ट वैल्यू में बदलाव किया जा सकता है:
def example_test(size="small", **kwargs):
_example_test(size=size, **kwargs)
_example_test = rule(
...
)
रनफ़ाइल की जगह
जब किसी एक्ज़ीक्यूटेबल टारगेट को bazel run
(या test
) के साथ चलाया जाता है, तो रनफ़ाइल डायरेक्ट्री का रूट, एक्ज़ीक्यूटेबल के बगल में होता है. पाथ इस तरह से जुड़े होते हैं:
# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
runfiles_root, workspace_name, runfile_path)
runfiles डायरेक्ट्री में मौजूद File
का पाथ, File.short_path
से मेल खाता है.
bazel
से सीधे तौर पर एक्ज़ीक्यूट किया गया बाइनरी, runfiles
डायरेक्ट्री के रूट के बगल में मौजूद है. हालांकि, रनफ़ाइल से from किए गए बाइनरी, एक ही अनुमान नहीं लगा सकते. इस समस्या को कम करने के लिए, हर बाइनरी को एनवायरमेंट या कमांड लाइन आर्ग्युमेंट/फ़्लैग का इस्तेमाल करके, अपने रनफ़ाइल रूट को पैरामीटर के तौर पर स्वीकार करने का तरीका देना चाहिए. इससे बाइनरी, उन बाइनरी को सही कैननिकल रनफ़ाइल रूट पास कर पाती हैं जिन्हें वह कॉल करती है. अगर इसे सेट नहीं किया जाता है, तो बाइनरी यह अनुमान लगा सकती है कि इसे पहली बार कॉल किया गया था. साथ ही, यह आस-पास की रनफ़ाइल डायरेक्ट्री ढूंढ सकती है.
उन्नत विषय
आउटपुट फ़ाइलों का अनुरोध करना
एक टारगेट के लिए कई आउटपुट फ़ाइलें हो सकती हैं. bazel build
कमांड चलाने पर, कमांड को दिए गए टारगेट के कुछ आउटपुट को अनुरोध किया गया माना जाता है. Bazel सिर्फ़ उन फ़ाइलों को बनाता है जिनके लिए अनुरोध किया गया है. साथ ही, उन फ़ाइलों को भी बनाता है जो सीधे या परोक्ष तौर पर उन फ़ाइलों पर निर्भर होती हैं. (ऐक्शन ग्राफ़ के हिसाब से, Bazel सिर्फ़ उन कार्रवाइयों को पूरा करता है जो अनुरोध की गई फ़ाइलों की ट्रांज़िटिव डिपेंडेंसी के तौर पर उपलब्ध हैं.)
डिफ़ॉल्ट आउटपुट के अलावा, कमांड लाइन पर किसी भी पहले से तय किए गए आउटपुट का अनुरोध किया जा सकता है. नियम, आउटपुट एट्रिब्यूट के ज़रिए पहले से तय किए गए आउटपुट तय कर सकते हैं. ऐसे में, उपयोगकर्ता नियम लागू करते समय आउटपुट के लिए लेबल चुनता है. आउटपुट एट्रिब्यूट के लिए File
ऑब्जेक्ट पाने के लिए, ctx.outputs
के संबंधित एट्रिब्यूट का इस्तेमाल करें. नियम, टारगेट के नाम के आधार पर पहले से तय किए गए आउटपुट को परोक्ष रूप से तय कर सकते हैं. हालांकि, अब इस सुविधा का इस्तेमाल नहीं किया जा सकता.
डिफ़ॉल्ट आउटपुट के अलावा, आउटपुट ग्रुप भी होते हैं. ये आउटपुट फ़ाइलों के ऐसे कलेक्शन होते हैं जिनके लिए एक साथ अनुरोध किया जा सकता है. इनके लिए --output_groups
का इस्तेमाल करके अनुरोध किया जा सकता है. उदाहरण के लिए, अगर कोई टारगेट //pkg:mytarget
ऐसे नियम टाइप का है जिसमें debug_files
आउटपुट ग्रुप है, तो इन फ़ाइलों को bazel build //pkg:mytarget
--output_groups=debug_files
चलाकर बनाया जा सकता है. पहले से तय नहीं किए गए आउटपुट में लेबल नहीं होते. इसलिए, इन्हें सिर्फ़ डिफ़ॉल्ट आउटपुट या आउटपुट ग्रुप में शामिल करके अनुरोध किया जा सकता है.
आउटपुट ग्रुप को OutputGroupInfo
प्रोवाइडर के साथ दिखाया जा सकता है. ध्यान दें कि कई बिल्ट-इन प्रोवाइडर के उलट, OutputGroupInfo
किसी भी नाम वाले पैरामीटर को इस्तेमाल कर सकता है. इससे उस नाम के आउटपुट ग्रुप तय किए जा सकते हैं:
def _example_library_impl(ctx):
...
debug_file = ctx.actions.declare_file(name + ".pdb")
...
return [
DefaultInfo(files = depset([output_file]), ...),
OutputGroupInfo(
debug_files = depset([debug_file]),
all_files = depset([output_file, debug_file]),
),
...
]
साथ ही, ज़्यादातर प्रोवाइडर के उलट, OutputGroupInfo
को पहलू और उस नियम के टारगेट, दोनों से वापस पाया जा सकता है जिस पर वह पहलू लागू होता है. हालांकि, ऐसा तब ही किया जा सकता है, जब वे एक ही आउटपुट ग्रुप को तय न करें. ऐसे में, नतीजे में मिले सेवा देने वाले लोगों या कंपनियों की जानकारी को मर्ज कर दिया जाता है.
ध्यान दें कि OutputGroupInfo
का इस्तेमाल आम तौर पर, टारगेट से उसके उपभोक्ताओं की कार्रवाइयों तक फ़ाइलों के खास तरह के डेटा को पहुंचाने के लिए नहीं किया जाना चाहिए. इसके बजाय, नियम के हिसाब से सेवा देने वाली कंपनियां तय करें.
कॉन्फ़िगरेशन
मान लें कि आपको किसी दूसरे आर्किटेक्चर के लिए C++ बाइनरी बनानी है. बनाने की प्रोसेस जटिल हो सकती है और इसमें कई चरण शामिल हो सकते हैं. कुछ इंटरमीडिएट बाइनरी, जैसे कि कंपाइलर और कोड जनरेटर को एक्ज़ीक्यूशन प्लैटफ़ॉर्म पर चलाना होता है. यह आपका होस्ट या रिमोट एक्ज़ीक्यूटर हो सकता है. फ़ाइनल आउटपुट जैसे कुछ बाइनरी को टारगेट आर्किटेक्चर के लिए बनाया जाना चाहिए.
इस वजह से, Bazel में "कॉन्फ़िगरेशन" और ट्रांज़िशन का कॉन्सेप्ट होता है. सबसे ऊपर मौजूद टारगेट (कमांड लाइन पर अनुरोध किए गए टारगेट) "target" कॉन्फ़िगरेशन में बनाए जाते हैं. वहीं, एक्ज़ीक्यूशन प्लैटफ़ॉर्म पर चलने वाले टूल, "exec" कॉन्फ़िगरेशन में बनाए जाते हैं. कॉन्फ़िगरेशन के आधार पर, नियमों से अलग-अलग कार्रवाइयां जनरेट हो सकती हैं. उदाहरण के लिए, कंपाइलर को पास किए गए सीपीयू आर्किटेक्चर को बदलना. कुछ मामलों में, अलग-अलग कॉन्फ़िगरेशन के लिए एक ही लाइब्रेरी की ज़रूरत पड़ सकती है. अगर ऐसा होता है, तो इसका विश्लेषण किया जाएगा और इसे कई बार बनाया जा सकता है.
डिफ़ॉल्ट रूप से, Bazel किसी टारगेट की डिपेंडेंसी को उसी कॉन्फ़िगरेशन में बनाता है जिसमें टारगेट खुद होता है. दूसरे शब्दों में कहें, तो ट्रांज़िशन के बिना. जब कोई डिपेंडेंसी ऐसा टूल हो जिसकी ज़रूरत टारगेट बनाने के लिए होती है, तो उससे जुड़े एट्रिब्यूट में, एक्ज़ेक कॉन्फ़िगरेशन में ट्रांज़िशन के बारे में जानकारी होनी चाहिए. इससे टूल और उसकी सभी डिपेंडेंसी, एक्ज़ीक्यूशन प्लैटफ़ॉर्म के लिए बनाई जाती हैं.
हर डिपेंडेंसी एट्रिब्यूट के लिए, cfg
का इस्तेमाल करके यह तय किया जा सकता है कि डिपेंडेंसी को एक ही कॉन्फ़िगरेशन में बनाया जाना चाहिए या किसी exec कॉन्फ़िगरेशन में ट्रांज़िशन करना चाहिए.
अगर किसी डिपेंडेंसी एट्रिब्यूट में executable=True
फ़्लैग है, तो cfg
को साफ़ तौर पर सेट किया जाना चाहिए. ऐसा इसलिए किया जाता है, ताकि गलती से गलत कॉन्फ़िगरेशन के लिए टूल न बन जाए.
उदाहरण देखें
आम तौर पर, रनटाइम के दौरान ज़रूरी सोर्स, डिपेंडेंट लाइब्रेरी, और एक्ज़ीक्यूटेबल के लिए एक ही कॉन्फ़िगरेशन का इस्तेमाल किया जा सकता है.
बिल्ड प्रोसेस के दौरान इस्तेमाल किए जाने वाले टूल (जैसे कि कंपाइलर या कोड जनरेटर) को exec कॉन्फ़िगरेशन के लिए बनाया जाना चाहिए. इस मामले में, एट्रिब्यूट में cfg="exec"
की वैल्यू दें.
इसके अलावा, रनटाइम के दौरान इस्तेमाल किए जाने वाले एक्ज़ीक्यूटेबल (जैसे कि टेस्ट के हिस्से के तौर पर) को टारगेट कॉन्फ़िगरेशन के लिए बनाया जाना चाहिए. इस मामले में, एट्रिब्यूट में cfg="target"
की वैल्यू दें.
cfg="target"
से कोई कार्रवाई नहीं होती. यह सिर्फ़ एक सुविधा है, ताकि नियम बनाने वाले लोग अपने इरादों के बारे में साफ़ तौर पर बता सकें. जब executable=False
हो, इसका मतलब है कि cfg
ज़रूरी नहीं है. इसे सिर्फ़ तब सेट करें, जब इससे पढ़ने में आसानी हो.
cfg=my_transition
का इस्तेमाल करके, उपयोगकर्ता के तय किए गए ट्रांज़िशन का इस्तेमाल किया जा सकता है. इससे नियम बनाने वाले लोगों को कॉन्फ़िगरेशन में बदलाव करने की सुविधा मिलती है. हालांकि, इससे बिल्ड ग्राफ़ बड़ा हो जाता है और उसे समझना मुश्किल हो जाता है.
ध्यान दें: पहले, Bazel में एक्ज़ीक्यूशन प्लैटफ़ॉर्म का कॉन्सेप्ट नहीं था. इसके बजाय, सभी बिल्ड ऐक्शन को होस्ट मशीन पर चलाने के लिए माना जाता था. Bazel के 6.0 से पहले के वर्शन, इसे दिखाने के लिए अलग "होस्ट" कॉन्फ़िगरेशन बनाते थे. अगर आपको कोड या पुराने दस्तावेज़ में "होस्ट" के रेफ़रंस दिखते हैं, तो इसका मतलब यही है. हमारा सुझाव है कि इस अतिरिक्त कॉन्सेप्ट ओवरहेड से बचने के लिए, Bazel 6.0 या इसके नए वर्शन का इस्तेमाल करें.
कॉन्फ़िगरेशन फ़्रैगमेंट
नियम, कॉन्फ़िगरेशन फ़्रैगमेंट को ऐक्सेस कर सकते हैं. जैसे, cpp
, java
, और jvm
. हालांकि, ऐक्सेस से जुड़ी गड़बड़ियों से बचने के लिए, सभी ज़रूरी फ़्रैगमेंट का एलान करना ज़रूरी है:
def _impl(ctx):
# Using ctx.fragments.cpp leads to an error since it was not declared.
x = ctx.fragments.java
...
my_rule = rule(
implementation = _impl,
fragments = ["java"], # Required fragments of the target configuration
host_fragments = ["java"], # Required fragments of the host configuration
...
)
रनफ़ाइल सिमलिंक
आम तौर पर, रनफ़ाइल ट्री में किसी फ़ाइल का रिलेटिव पाथ वही होता है जो सोर्स ट्री या जनरेट किए गए आउटपुट ट्री में उस फ़ाइल का रिलेटिव पाथ होता है. अगर किसी वजह से इन्हें अलग-अलग रखना है, तो root_symlinks
या symlinks
आर्ग्युमेंट तय किए जा सकते हैं. root_symlinks
एक डिक्शनरी है, जो पाथ को फ़ाइलों पर मैप करती है. इसमें पाथ, रनफ़ाइल डायरेक्ट्री के रूट के हिसाब से होते हैं. symlinks
डिक्शनरी एक जैसी होती है, लेकिन पाथ में मुख्य वर्कस्पेस का नाम (न कि मौजूदा टारगेट वाली रिपॉज़िटरी का नाम) पहले से मौजूद होता है.
...
runfiles = ctx.runfiles(
root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
)
# Creates something like:
# sometarget.runfiles/
# some/
# path/
# here.foo -> some_data_file2
# <workspace_name>/
# some/
# path/
# here.bar -> some_data_file3
अगर symlinks
या root_symlinks
का इस्तेमाल किया जाता है, तो ध्यान रखें कि रनफ़ाइल ट्री में दो अलग-अलग फ़ाइलों को एक ही पाथ पर मैप न किया जाए. इससे बिल्ड पूरा नहीं हो पाएगा. साथ ही, गड़बड़ी के बारे में जानकारी देने वाला मैसेज दिखेगा. इस समस्या को ठीक करने के लिए, आपको अपने ctx.runfiles
आर्ग्युमेंट में बदलाव करना होगा, ताकि टकराव को हटाया जा सके. यह जांच, आपके नियम का इस्तेमाल करने वाले सभी टारगेट के लिए की जाएगी. साथ ही, उन सभी टारगेट के लिए भी की जाएगी जो इन टारगेट पर निर्भर हैं. अगर आपके टूल का इस्तेमाल किसी दूसरे टूल के ज़रिए किया जाता है, तो यह खास तौर पर जोखिम भरा होता है. किसी टूल की रनफ़ाइल और उसकी सभी डिपेंडेंसी के लिए, सिमलंक के नाम यूनीक होने चाहिए.
कोड कवरेज
coverage
कमांड चलाने पर, बिल्ड को कुछ टारगेट के लिए कवरेज इंस्ट्रूमेंटेशन जोड़ने की ज़रूरत पड़ सकती है. बिल्ड, इंस्ट्रुमेंट की गई सोर्स फ़ाइलों की सूची भी इकट्ठा करता है. टारगेट के जिस सबसेट पर विचार किया जाता है उसे --instrumentation_filter
फ़्लैग से कंट्रोल किया जाता है.
टेस्ट टारगेट को बाहर रखा जाता है, जब तक कि --instrument_test_targets
को शामिल न किया जाए.
अगर किसी नियम को लागू करने से, बिल्ड के समय कवरेज इंस्ट्रूमेंटेशन जुड़ जाता है, तो उसे लागू करने वाले फ़ंक्शन में इसका हिसाब रखना होगा. अगर किसी टारगेट के सोर्स को इंस्ट्रूमेंट किया जाना चाहिए, तो कवरेज मोड में ctx.coverage_instrumented की वैल्यू सही होती है:
# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
# Do something to turn on coverage for this compile action
कवरेज मोड में हमेशा चालू रहने वाले लॉजिक (चाहे टारगेट के सोर्स खास तौर पर इंस्ट्रुमेंट किए गए हों या नहीं) को ctx.configuration.coverage_enabled के आधार पर तय किया जा सकता है.
अगर नियम में, कंपाइल करने से पहले ही डिपेंडेंसी के सोर्स शामिल हैं (जैसे कि हेडर फ़ाइलें), तो हो सकता है कि उसे कंपाइल-टाइम इंस्ट्रूमेंटेशन भी चालू करना पड़े. ऐसा तब होता है, जब डिपेंडेंसी के सोर्स को इंस्ट्रूमेंट किया जाना हो:
# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
(ctx.coverage_instrumented() or
any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
# Do something to turn on coverage for this compile action
नियमों में यह जानकारी भी होनी चाहिए कि InstrumentedFilesInfo
सेवा देने वाली कंपनी के साथ कवरेज के लिए, कौनसे एट्रिब्यूट काम के हैं. ये एट्रिब्यूट, coverage_common.instrumented_files_info
का इस्तेमाल करके बनाए जाते हैं.
instrumented_files_info
के dependency_attributes
पैरामीटर में, रनटाइम डिपेंडेंसी वाले सभी एट्रिब्यूट की सूची होनी चाहिए. इसमें कोड डिपेंडेंसी वाले एट्रिब्यूट, जैसे कि deps
और डेटा डिपेंडेंसी वाले एट्रिब्यूट, जैसे कि data
शामिल हैं. अगर कवरेज इंस्ट्रूमेंटेशन जोड़ा जा सकता है, तो source_attributes
पैरामीटर में नियम की सोर्स फ़ाइलों के एट्रिब्यूट की सूची होनी चाहिए:
def _example_library_impl(ctx):
...
return [
...
coverage_common.instrumented_files_info(
ctx,
dependency_attributes = ["deps", "data"],
# Omitted if coverage is not supported for this rule:
source_attributes = ["srcs", "hdrs"],
)
...
]
अगर InstrumentedFilesInfo
नहीं मिलता है, तो हर ऐसे डिपेंडेंसी एट्रिब्यूट के लिए डिफ़ॉल्ट वैल्यू बनाई जाती है जो dependency_attributes
में, एट्रिब्यूट स्कीमा में cfg
को "host"
या "exec"
पर सेट नहीं करता है. (यह तरीका सही नहीं है, क्योंकि इससे srcs
जैसे एट्रिब्यूट, source_attributes
के बजाय dependency_attributes
में चले जाते हैं. हालांकि, इससे डिपेंडेंसी चेन में मौजूद सभी नियमों के लिए, कवरेज कॉन्फ़िगरेशन को साफ़ तौर पर बताने की ज़रूरत नहीं पड़ती.)
पुष्टि करने की कार्रवाइयां
कभी-कभी आपको बिल्ड के बारे में किसी चीज़ की पुष्टि करनी होती है. इसके लिए ज़रूरी जानकारी, सिर्फ़ आर्टफ़ैक्ट (सोर्स फ़ाइलें या जनरेट की गई फ़ाइलें) में उपलब्ध होती है. यह जानकारी आर्टफ़ैक्ट में मौजूद होती है. इसलिए, नियमों के आधार पर विश्लेषण के दौरान इसकी पुष्टि नहीं की जा सकती. ऐसा इसलिए होता है, क्योंकि नियम फ़ाइलें नहीं पढ़ सकते. इसके बजाय, कार्रवाइयों को लागू करते समय, इस पुष्टि को पूरा करना होगा. पुष्टि न होने पर, कार्रवाई पूरी नहीं होगी. इसलिए, बिल्ड भी नहीं होगा.
पुष्टि करने के लिए, स्टैटिक विश्लेषण, लिंटिंग, डिपेंडेंसी और कंसिस्टेंसी की जांच, और स्टाइल की जांच की जा सकती है.
पुष्टि करने वाली कार्रवाइयां, बिल्ड की परफ़ॉर्मेंस को बेहतर बनाने में भी मदद कर सकती हैं. इसके लिए, वे कार्रवाइयों के उन हिस्सों को अलग कार्रवाइयों में ले जाती हैं जिनकी ज़रूरत आर्टफ़ैक्ट बनाने के लिए नहीं होती. उदाहरण के लिए, अगर कंपाइल करने और लिंटिंग करने वाली किसी एक कार्रवाई को कंपाइल करने की कार्रवाई और लिंटिंग करने की कार्रवाई में अलग किया जा सकता है, तो लिंटिंग करने की कार्रवाई को पुष्टि करने की कार्रवाई के तौर पर चलाया जा सकता है. साथ ही, इसे अन्य कार्रवाइयों के साथ-साथ चलाया जा सकता है.
ये "पुष्टि करने वाली कार्रवाइयां" अक्सर ऐसी कोई चीज़ नहीं बनाती हैं जिसका इस्तेमाल बिल्ड में कहीं और किया जाता है. ऐसा इसलिए, क्योंकि उन्हें सिर्फ़ अपने इनपुट के बारे में पुष्टि करनी होती है. हालांकि, इससे एक समस्या पैदा होती है: अगर पुष्टि करने की कार्रवाई से ऐसा कुछ नहीं मिलता जिसका इस्तेमाल बिल्ड में कहीं और किया जाता है, तो नियम को कार्रवाई चलाने के लिए कैसे मिलेगा? पहले, पुष्टि करने वाले ऐक्शन का आउटपुट एक खाली फ़ाइल होती थी. इस आउटपुट को, बिल्ड में मौजूद किसी अन्य ज़रूरी ऐक्शन के इनपुट में आर्टिफ़िशियली जोड़ा जाता था:
यह काम करता है, क्योंकि Bazel, कंपाइल करने की कार्रवाई के दौरान हमेशा पुष्टि करने की कार्रवाई करता है. हालांकि, इसके कुछ नुकसान हैं:
पुष्टि करने की कार्रवाई, बिल्ड के क्रिटिकल पाथ में है. Bazel को लगता है कि कंपाइल ऐक्शन को चलाने के लिए, खाली आउटपुट की ज़रूरत होती है. इसलिए, वह पहले पुष्टि करने वाला ऐक्शन चलाएगा. भले ही, कंपाइल ऐक्शन इनपुट को अनदेखा कर देगा. इससे पैरललिज़्म कम हो जाता है और बिल्ड धीमे हो जाते हैं.
अगर बिल्ड में कंपाइल ऐक्शन के बजाय अन्य ऐक्शन चल सकते हैं, तो पुष्टि करने वाले ऐक्शन के खाली आउटपुट को उन ऐक्शन में भी जोड़ना होगा. उदाहरण के लिए,
java_library
का सोर्स जार आउटपुट. अगर बाद में, कंपाइल ऐक्शन के बजाय चलने वाले नए ऐक्शन जोड़े जाते हैं और गलती से पुष्टि करने का खाली आउटपुट छोड़ दिया जाता है, तो भी यह समस्या होती है.
इन समस्याओं को हल करने के लिए, पुष्टि करने से जुड़े आउटपुट ग्रुप का इस्तेमाल करें.
पुष्टि के नतीजों का ग्रुप
पुष्टि करने से जुड़े आउटपुट ग्रुप को, पुष्टि करने की कार्रवाइयों के ऐसे आउटपुट को सेव करने के लिए डिज़ाइन किया गया है जिनका इस्तेमाल नहीं किया जाता. इससे, उन्हें अन्य कार्रवाइयों के इनपुट में मैन्युअल तरीके से जोड़ने की ज़रूरत नहीं पड़ती.
यह ग्रुप खास है. इसके आउटपुट हमेशा अनुरोध किए जाते हैं, भले ही --output_groups
फ़्लैग की वैल्यू कुछ भी हो. साथ ही, टारगेट किस तरह से निर्भर है, इससे भी कोई फ़र्क़ नहीं पड़ता. उदाहरण के लिए, कमांड लाइन पर, डिपेंडेंसी के तौर पर या टारगेट के इंप्लिसिट आउटपुट के ज़रिए. ध्यान दें कि सामान्य कैश मेमोरी और इंक्रीमेंटैलिटी अब भी लागू होती है: अगर पुष्टि करने की कार्रवाई के इनपुट में कोई बदलाव नहीं हुआ है और पुष्टि करने की कार्रवाई पहले सफल हो गई थी, तो पुष्टि करने की कार्रवाई नहीं की जाएगी.
इस आउटपुट ग्रुप का इस्तेमाल करने के लिए, पुष्टि करने वाली कार्रवाइयों को अब भी कुछ फ़ाइलें आउटपुट करनी होंगी. भले ही, वे फ़ाइलें खाली हों. इसके लिए, कुछ ऐसे टूल को रैप करने की ज़रूरत पड़ सकती है जो आम तौर पर आउटपुट नहीं बनाते हैं, ताकि एक फ़ाइल बनाई जा सके.
पुष्टि करने की कार्रवाइयां, तीन मामलों में टारगेट पर नहीं चलाई जाती हैं:
- जब टारगेट को टूल के तौर पर इस्तेमाल किया जाता है
- जब टारगेट को इंप्लिसिट डिपेंडेंसी के तौर पर इस्तेमाल किया जाता है. उदाहरण के लिए, "_" से शुरू होने वाला कोई एट्रिब्यूट
- जब टारगेट को होस्ट या एक्ज़ेक कॉन्फ़िगरेशन में बनाया जाता है.
यह माना जाता है कि इन टारगेट के अपने अलग-अलग बिल्ड और टेस्ट होते हैं, जिनसे पुष्टि करने से जुड़ी किसी भी गड़बड़ी का पता चल जाएगा.
मान्यताओं के आउटपुट ग्रुप का इस्तेमाल करना
मान्यताओं के आउटपुट ग्रुप का नाम _validation
है. इसका इस्तेमाल किसी अन्य आउटपुट ग्रुप की तरह ही किया जाता है:
def _rule_with_validation_impl(ctx):
ctx.actions.write(ctx.outputs.main, "main output\n")
ctx.actions.write(ctx.outputs.implicit, "implicit output\n")
validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
ctx.actions.run(
outputs = [validation_output],
executable = ctx.executable._validation_tool,
arguments = [validation_output.path])
return [
DefaultInfo(files = depset([ctx.outputs.main])),
OutputGroupInfo(_validation = depset([validation_output])),
]
rule_with_validation = rule(
implementation = _rule_with_validation_impl,
outputs = {
"main": "%{name}.main",
"implicit": "%{name}.implicit",
},
attrs = {
"_validation_tool": attr.label(
default = Label("//validation_actions:validation_tool"),
executable = True,
cfg = "exec"),
}
)
ध्यान दें कि पुष्टि करने के बाद मिली आउटपुट फ़ाइल को DefaultInfo
या किसी अन्य कार्रवाई के इनपुट में नहीं जोड़ा जाता. अगर टारगेट, लेबल पर निर्भर करता है या टारगेट के किसी भी इंप्लिसिट आउटपुट पर सीधे या परोक्ष तौर पर निर्भर करता है, तो इस तरह के नियम के टारगेट के लिए पुष्टि करने की कार्रवाई अब भी चलेगी.
आम तौर पर, यह ज़रूरी होता है कि पुष्टि करने वाली कार्रवाइयों के आउटपुट सिर्फ़ पुष्टि करने वाले आउटपुट ग्रुप में जाएं. साथ ही, उन्हें अन्य कार्रवाइयों के इनपुट में न जोड़ा जाए. ऐसा इसलिए, क्योंकि इससे पैरललिज़्म के फ़ायदे कम हो सकते हैं. हालांकि, ध्यान दें कि Bazel में फ़िलहाल इस नियम को लागू करने के लिए, कोई खास जांच नहीं की जाती. इसलिए, आपको यह जांच करनी चाहिए कि पुष्टि करने की कार्रवाई के आउटपुट, Starlark नियमों के टेस्ट में किसी भी कार्रवाई के इनपुट में न जोड़े गए हों. उदाहरण के लिए:
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
def _validation_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
target = analysistest.target_under_test(env)
validation_outputs = target.output_groups._validation.to_list()
for action in actions:
for validation_output in validation_outputs:
if validation_output in action.inputs.to_list():
analysistest.fail(env,
"%s is a validation action output, but is an input to action %s" % (
validation_output, action))
return analysistest.end(env)
validation_outputs_test = analysistest.make(_validation_outputs_test_impl)
पुष्टि से जुड़ी कार्रवाइयों के लिए फ़्लैग
पुष्टि करने वाली कार्रवाइयों को चलाने की सुविधा, --run_validations
कमांड लाइन फ़्लैग से कंट्रोल की जाती है. इसकी डिफ़ॉल्ट वैल्यू सही होती है.
बंद की गई सुविधाएं
पहले से बताए गए आउटपुट बंद कर दिए गए हैं
पहले से तय किए गए आउटपुट का इस्तेमाल करने के दो तरीके बंद कर दिए गए हैं:
rule
काoutputs
पैरामीटर, आउटपुट एट्रिब्यूट के नामों और स्ट्रिंग टेंप्लेट के बीच मैपिंग तय करता है. इससे पहले से तय किए गए आउटपुट लेबल जनरेट किए जाते हैं. पहले से तय नहीं किए गए आउटपुट का इस्तेमाल करें औरDefaultInfo.files
में आउटपुट साफ़ तौर पर जोड़ें. नियम के टारगेट के लेबल का इस्तेमाल उन नियमों के लिए इनपुट के तौर पर करें जो आउटपुट का इस्तेमाल करते हैं. इसके बजाय, पहले से तय किए गए आउटपुट के लेबल का इस्तेमाल करें.लागू किए जा सकने वाले नियमों के लिए,
ctx.outputs.executable
से मतलब है कि लागू किए जा सकने वाले ऐसे आउटपुट से जिसे पहले से तय किया गया है और जिसका नाम, नियम के टारगेट के नाम जैसा है. आउटपुट को साफ़ तौर पर एलान करने का सुझाव दिया जाता है. उदाहरण के लिए,ctx.actions.declare_file(ctx.label.name)
का इस्तेमाल करें. साथ ही, पक्का करें कि एक्ज़ीक्यूटेबल जनरेट करने वाला निर्देश, उसे एक्ज़ीक्यूट करने की अनुमति दे. एक्ज़ीक्यूटेबल कोड के आउटपुट कोDefaultInfo
केexecutable
पैरामीटर में साफ़ तौर पर पास करें.
रनफ़ाइल की इन सुविधाओं का इस्तेमाल न करें
ctx.runfiles
और runfiles
टाइप में कई सुविधाएं होती हैं. इनमें से कई सुविधाएं, लेगसी वजहों से उपलब्ध कराई जाती हैं.
यहां दिए गए सुझावों से, जटिलता को कम करने में मदद मिलती है:
ctx.runfiles
केcollect_data
औरcollect_default
मोड का इस्तेमाल न करें. ये मोड, कुछ हार्डकोड किए गए डिपेंडेंसी एज में रनफ़ाइल को इकट्ठा करते हैं. हालांकि, ऐसा भ्रमित करने वाले तरीकों से किया जाता है. इसके बजाय,ctx.runfiles
केfiles
याtransitive_files
पैरामीटर का इस्तेमाल करके फ़ाइलें जोड़ें. इसके अलावा,runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
का इस्तेमाल करके, डिपेंडेंसी से रनफ़ाइलें मर्ज की जा सकती हैं.DefaultInfo
कंस्ट्रक्टर केdata_runfiles
औरdefault_runfiles
का इस्तेमाल न करें. इसके बजाय,DefaultInfo(runfiles = ...)
बताएं. लेगसी सिस्टम के साथ काम करने के लिए, "डिफ़ॉल्ट" और "डेटा" रनफ़ाइल के बीच अंतर बनाए रखा जाता है. उदाहरण के लिए, कुछ नियम अपने डिफ़ॉल्ट आउटपुट कोdata_runfiles
में रखते हैं, लेकिनdefault_runfiles
में नहीं.data_runfiles
का इस्तेमाल करने के बजाय, नियमों में डिफ़ॉल्ट आउटपुट दोनों शामिल होने चाहिए. साथ ही, रनफ़ाइल देने वाले एट्रिब्यूट सेdefault_runfiles
को मर्ज करना चाहिए. अक्सर, रनफ़ाइल देने वाले एट्रिब्यूटdata
होते हैं.DefaultInfo
सेrunfiles
को वापस पाने के लिए (आम तौर पर, इसका इस्तेमाल सिर्फ़ मौजूदा नियम और उसकी डिपेंडेंसी के बीच रनफ़ाइल मर्ज करने के लिए किया जाता है),DefaultInfo.default_runfiles
का इस्तेमाल करें, न किDefaultInfo.data_runfiles
का.
लेगसी सेवा देने वाली कंपनियों से माइग्रेट करना
पहले, Bazel के प्रोवाइडर, Target
ऑब्जेक्ट पर मौजूद सामान्य फ़ील्ड होते थे. इन्हें डॉट ऑपरेटर का इस्तेमाल करके ऐक्सेस किया गया था. साथ ही, इन्हें फ़ील्ड को नियम लागू करने वाले फ़ंक्शन से मिले स्ट्रक्चर में रखकर बनाया गया था.
इस स्टाइल को बंद कर दिया गया है. इसलिए, इसका इस्तेमाल नए कोड में नहीं किया जाना चाहिए; माइग्रेट करने में आपकी मदद करने वाली जानकारी के लिए, यहां दिया गया लिंक देखें. नए प्रोवाइडर मैकेनिज़्म से, नाम के टकराव से बचा जा सकता है. यह डेटा छिपाने की सुविधा भी देता है. इसके लिए, किसी भी कोड को प्रोवाइडर इंस्टेंस ऐक्सेस करने के लिए, प्रोवाइडर सिंबल का इस्तेमाल करके उसे वापस पाना होता है.
फ़िलहाल, लेगसी सेवा देने वाली कंपनियां अब भी काम करती हैं. कोई नियम, लेगसी और मॉडर्न, दोनों तरह के प्रोवाइडर को इस तरह से दिखा सकता है:
def _old_rule_impl(ctx):
...
legacy_data = struct(x="foo", ...)
modern_data = MyInfo(y="bar", ...)
# When any legacy providers are returned, the top-level returned value is a
# struct.
return struct(
# One key = value entry for each legacy provider.
legacy_info = legacy_data,
...
# Additional modern providers:
providers = [modern_data, ...])
अगर इस नियम के किसी इंस्टेंस के लिए, dep
, Target
ऑब्जेक्ट है, तो सेवा देने वाली कंपनियों और उनके कॉन्टेंट को dep.legacy_info.x
और dep[MyInfo].y
के तौर पर वापस पाया जा सकता है.
providers
के अलावा, लौटाए गए स्ट्रक्चर में कई अन्य फ़ील्ड भी हो सकते हैं. इनका खास मतलब होता है. इसलिए, ये मिलते-जुलते लेगसी प्रोवाइडर नहीं बनाते:
files
,runfiles
,data_runfiles
,default_runfiles
, औरexecutable
फ़ील्ड,DefaultInfo
के इसी नाम वाले फ़ील्ड से मेल खाते हैं.DefaultInfo
प्रोवाइडर को वापस लाते समय, इनमें से किसी भी फ़ील्ड को तय करने की अनुमति नहीं है.output_groups
फ़ील्ड, स्ट्रक्चर वैल्यू लेता है और यहOutputGroupInfo
से मेल खाता है.
provides
नियमों के एलान और providers
निर्भरता एट्रिब्यूट के एलान में, लेगसी सेवा देने वाली कंपनियों को स्ट्रिंग के तौर पर पास किया जाता है. वहीं, आधुनिक सेवा देने वाली कंपनियों को उनके *Info
सिंबल के ज़रिए पास किया जाता है. माइग्रेट करते समय, स्ट्रिंग को सिंबल में बदलना न भूलें. नियमों के जटिल या बड़े सेट के लिए, जहां सभी नियमों को एक साथ अपडेट करना मुश्किल होता है, इन चरणों को इस क्रम में पूरा करने से आपको आसानी हो सकती है:
ऊपर दिए गए सिंटैक्स का इस्तेमाल करके, लेगसी प्रोवाइडर को जनरेट करने वाले नियमों में बदलाव करें, ताकि लेगसी और मॉडर्न प्रोवाइडर, दोनों जनरेट किए जा सकें. जिन नियमों में यह एलान किया गया है कि वे लेगसी सेवा देने वाली कंपनी को डेटा वापस भेजते हैं उनके लिए, एलान को अपडेट करें. इसमें लेगसी और मॉडर्न सेवा देने वाली कंपनी, दोनों को शामिल करें.
लेगसी सेवा देने वाली कंपनी का इस्तेमाल करने वाले नियमों में बदलाव करें, ताकि वे आधुनिक सेवा देने वाली कंपनी का इस्तेमाल कर सकें. अगर किसी एट्रिब्यूट के एलान के लिए लेगसी प्रोवाइडर की ज़रूरत है, तो उसे भी अपडेट करके मॉडर्न प्रोवाइडर की ज़रूरत के हिसाब से सेट करें. इसके अलावा, आपके पास इस काम को पहले चरण के साथ इंटरलीव करने का विकल्प भी है. इसके लिए, उपभोक्ताओं को इनमें से किसी एक को स्वीकार/ज़रूरी करना होगा: सीएमपी सेवा देने वाली कंपनी:
hasattr(target, 'foo')
का इस्तेमाल करके, लेगसी सीएमपी सेवा देने वाली कंपनी की मौजूदगी की जांच करें याFooInfo in target
का इस्तेमाल करके, नई सीएमपी सेवा देने वाली कंपनी की मौजूदगी की जांच करें.लेगसी प्रोवाइडर को सभी नियमों से पूरी तरह हटा दें.