नियम ऐसी कार्रवाइयों की सीरीज़ के बारे में बताता है जिन्हें बेज़ल, इनपुट का इस्तेमाल करके आउटपुट का सेट बनाने के लिए करते हैं. इन आउटपुट का सेट, नियम के लागू करने का फ़ंक्शन से दिए गए providers में बताया जाता है. उदाहरण के लिए, C++ बाइनरी नियम के तहत ये काम किए जा सकते हैं:
.cpp
सोर्स फ़ाइलों (इनपुट) का सेट लें.- सोर्स फ़ाइलों (कार्रवाई) पर
g++
चलाएं. - रनटाइम के दौरान उपलब्ध कराने के लिए,
DefaultInfo
प्रोवाइडर को एक्ज़ीक्यूटेबल आउटपुट और अन्य फ़ाइलों के साथ लौटाना. - टारगेट और उसकी डिपेंडेंसी से इकट्ठा की गई C++ की खास जानकारी के साथ,
CcInfo
प्रोवाइडर को वापस भेजें.
बेज़ल के हिसाब से, इस नियम में g++
और स्टैंडर्ड C++ लाइब्रेरी भी इनपुट हैं. नियम लिखने वाले के तौर पर, आपको नियम में उपयोगकर्ता से मिले इनपुट के साथ-साथ, कार्रवाइयों को लागू करने के लिए ज़रूरी सभी टूल और लाइब्रेरी पर भी ध्यान देना चाहिए.
किसी भी नियम को बनाने या उसमें बदलाव करने से पहले, पक्का करें कि आपको Baज़र के बिल्ड फ़ेज़ के बारे में जानकारी हो. बिल्ड के तीन चरणों (लोडिंग, विश्लेषण, और एक्ज़ीक्यूशन) को समझना ज़रूरी है. नियमों और मैक्रो के बीच के अंतर को समझने के लिए, मैक्रो के बारे में जानना भी फ़ायदेमंद होता है. शुरू करने के लिए, पहले नियमों का ट्यूटोरियल देखें. इसके बाद, इस पेज को रेफ़रंस के तौर पर इस्तेमाल करें.
Basel में भी कुछ नियम बनाए गए हैं. ये नेटिव नियम, जैसे कि
cc_library
और java_binary
, कुछ भाषाओं के लिए काम करते हैं.
अपने नियम खुद तय करके, उन भाषाओं और टूल के लिए भी इससे मिलती-जुलती सुविधाएं जोड़ी जा सकती हैं
जिन पर Basel का इस्तेमाल स्थानीय तौर पर नहीं होता है.
Basel ने 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 मैक्रो कहा जाता है.
स्टारलार्क मैक्रो को सिर्फ़ BUILD
फ़ाइलों से कॉल किया जाना चाहिए. इन्हें सिर्फ़ लोड होने के चरण के दौरान ही कॉल किया जा सकता है. ऐसा तब होता है, जब टारगेट को इंस्टैंशिएट करने के लिए BUILD
फ़ाइलों का आकलन किया जाता है.
विशेषताएं
एट्रिब्यूट, नियम से जुड़ा आर्ग्युमेंट होता है. एट्रिब्यूट, किसी टारगेट के लागू होने के लिए खास वैल्यू दे सकते हैं. इसके अलावा, वे अन्य टारगेट का रेफ़रंस देकर, डिपेंडेंसी का ग्राफ़ बना सकते हैं.
srcs
या deps
जैसे नियम के हिसाब से बने एट्रिब्यूट तय करने के लिए, एट्रिब्यूट के नामों से स्कीमा (attr
मॉड्यूल का इस्तेमाल करके बनाए गए) तक मैप को rule
के attrs
पैरामीटर पर पास किया जाता है.
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),
...
},
)
ये डिपेंडेंसी एट्रिब्यूट के उदाहरण हैं. कोई भी एट्रिब्यूट, किसी टारगेट और उन टारगेट के बीच किसी खास तरह की डिपेंडेंसी की जानकारी देता है जिनके लेबल (या उनसे जुड़े Label
ऑब्जेक्ट) को टारगेट तय करते समय, उस एट्रिब्यूट में शामिल किया जाता है. इन टारगेट को attr.label_list
, attr.label
या attr.label_keyed_string_dict
एट्रिब्यूट की मदद से तय किया जाता है. इन लेबल के लिए, डेटा स्टोर करने की जगह और पाथ को तय किए गए टारगेट के हिसाब से हल किया जाता है.
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
नाम दिया जाता है. वे providers की सूची दिखाते हैं.
टारगेट
विश्लेषण के समय, डिपेंडेंसी को Target
ऑब्जेक्ट के तौर पर दिखाया जाता है. इन ऑब्जेक्ट में, टारगेट लागू करने वाले फ़ंक्शन को लागू करने पर जनरेट किए गए प्रोवाइडर शामिल होते हैं.
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
ऑब्जेक्ट से दिखाया जाता है. हालांकि, विश्लेषण के दौरान Baze, फ़ाइल 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
का इस्तेमाल, कार्रवाइयों के लिए आर्ग्युमेंट को बेहतर तरीके से इकट्ठा करने के लिए किया जा सकता है. यह प्रोसेस, डेटा को तब तक फ़्लैट नहीं करती, जब तक कि उसे लागू नहीं किया जाता:
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
डिफ़ॉल्ट रूप से पहले से तय किए गए आउटपुट पर सेट होता है. आम तौर पर, वे आउटपुट एट्रिब्यूट से बनाए जाते हैं.
कार्रवाइयां करने वाले नियमों में डिफ़ॉल्ट आउटपुट देने चाहिए, भले ही उन आउटपुट के सीधे इस्तेमाल किए जाने की उम्मीद न की गई हो. जो कार्रवाइयां अनुरोध किए गए आउटपुट के ग्राफ़ में नहीं हैं उन्हें काट दिया जाता है. अगर आउटपुट का इस्तेमाल सिर्फ़ टारगेट के उपभोक्ता करते हैं, तो टारगेट के अलग-अलग होने पर वे कार्रवाइयां नहीं की जाएंगी. इससे, डिबग करने की प्रोसेस ज़्यादा मुश्किल हो जाती है. ऐसा इसलिए, क्योंकि सिर्फ़ गड़बड़ी वाले टारगेट को फिर से बनाने पर, गड़बड़ी दोबारा नहीं दिखेगी.
रनफ़ाइल
रनफ़ाइलें, फ़ाइलों का एक सेट होता है. इसका इस्तेमाल टारगेट, रनटाइम के दौरान करता है, न कि बिल्डटाइम के दौरान. कार्रवाई के चरण के दौरान, Bazel एक डायरेक्ट्री ट्री बनाता है. इसमें, runfile पर ले जाने वाले सिंबल लिंक होते हैं. यह बाइनरी के लिए एनवायरमेंट के स्टेज में है, ताकि यह रनटाइम के दौरान रनफ़ाइल को ऐक्सेस कर सके.
नियम बनाने के दौरान रनफ़ाइल मैन्युअल रूप से जोड़ी जा सकती हैं.
runfiles
ऑब्जेक्ट, नियम के संदर्भ ctx.runfiles
पर runfiles
तरीके से बनाए जा सकते हैं और DefaultInfo
पर runfiles
पैरामीटर में पास किए जा सकते हैं. कार्रवाई करने वाले नियमों का, रनफ़ाइलों में अपने-आप जुड़ने वाला आउटपुट.
कुछ नियम, एट्रिब्यूट के बारे में बताते हैं. इन्हें आम तौर पर data
नाम दिया जाता है. इनके आउटपुट को टारगेट की रनफ़ाइल में जोड़ा जाता है. रनफ़ाइलों को data
के साथ-साथ, उन सभी एट्रिब्यूट से भी मर्ज किया जाना चाहिए जिनसे आखिर में एक्ज़ीक्यूशन के लिए कोड मिल सकता है. आम तौर पर, इनमें srcs
(इसमें data
से जुड़े filegroup
टारगेट हो सकते हैं) और
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
, दोनों में पास करने की ज़रूरत नहीं है. इसे रनफ़ाइल में भी अपने-आप जोड़ दिया जाता है:
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)
रनफ़ाइल डायरेक्ट्री के तहत बनाए गए File
का पाथ,
File.short_path
से जुड़ा है.
bazel
की मदद से सीधे बनाई गई बाइनरी, runfiles
डायरेक्ट्री के रूट के बगल में मौजूद होती है. हालांकि, रनफ़ाइल from वाली बाइनरी का इस्तेमाल नहीं किया जा सकता. इसे कम करने के लिए, हर बाइनरी को एनवायरमेंट या कमांड लाइन/फ़्लैग का इस्तेमाल करके, अपने रनफ़ाइल रूट को पैरामीटर के तौर पर स्वीकार करने का तरीका देना चाहिए. इससे बाइनरी कॉल करने वाली बाइनरी को सही कैननिकल रनफ़ाइल रूट
भेज सकती हैं. अगर इस नीति को सेट नहीं किया जाता है, तो बाइनरी यह अनुमान लगा सकती है कि यह कॉल की जाने वाली पहली बाइनरी है और आस-पास की रनफ़ाइल डायरेक्ट्री को खोजेगी.
उन्नत विषय
आउटपुट फ़ाइलों का अनुरोध किया जा रहा है
एक टारगेट में कई आउटपुट फ़ाइलें हो सकती हैं. bazel build
कमांड को चलाने पर, कमांड के लिए दिए गए टारगेट के कुछ आउटपुट को अनुरोध माना जाता है. Bazel सिर्फ़ उन फ़ाइलों और उन फ़ाइलों को बनाता है जिन पर ये फ़ाइलें सीधे या indirectly निर्भर करती हैं. (ऐक्शन ग्राफ़ के हिसाब से, 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 में "कॉन्फ़िगरेशन" और ट्रांज़िशन का कॉन्सेप्ट है. सबसे ऊपर मौजूद टारगेट (कमांड लाइन पर अनुरोध किए गए टारगेट), "टारगेट" कॉन्फ़िगरेशन में बनाए जाते हैं. वहीं, ऐसे टूल जिन्हें एक्सीक्यूशन प्लैटफ़ॉर्म पर चलाया जाना चाहिए उन्हें "exec" कॉन्फ़िगरेशन में बनाया जाता है. कॉन्फ़िगरेशन के आधार पर, नियम अलग-अलग कार्रवाइयां जनरेट कर सकते हैं. उदाहरण के लिए, कंपाइलर को पास किए गए सीपीयू आर्किटेक्चर को बदलने के लिए. कुछ मामलों में, अलग-अलग कॉन्फ़िगरेशन के लिए एक ही लाइब्रेरी की ज़रूरत हो सकती है. अगर ऐसा होता है, तो इसका विश्लेषण किया जाएगा और हो सकता है कि इसे कई बार बनाया जाए.
डिफ़ॉल्ट रूप से, Bazel किसी टारगेट की डिपेंडेंसी को उसी कॉन्फ़िगरेशन में बनाता है जिसमें टारगेट बना है. दूसरे शब्दों में, ट्रांज़िशन के बिना. जब कोई डिपेंडेंसी ऐसा टूल होता है जिसकी ज़रूरत टारगेट बनाने के लिए होती है, तो उससे जुड़े एट्रिब्यूट में, एक्ज़ीक्यूट कॉन्फ़िगरेशन में ट्रांज़िशन की जानकारी होनी चाहिए. इसी वजह से, टूल और इसकी सभी डिपेंडेंसी, एक्ज़ीक्यूशन प्लैटफ़ॉर्म के लिए तैयार होती हैं.
हर डिपेंडेंसी एट्रिब्यूट के लिए, cfg
का इस्तेमाल किया जा सकता है. इससे यह तय किया जा सकता है कि डिपेंडेंसी को एक ही कॉन्फ़िगरेशन में बनाया जाना चाहिए या किसी एक्ज़ेक्यूटिव कॉन्फ़िगरेशन में बदलना चाहिए.
अगर किसी डिपेंडेंसी एट्रिब्यूट में executable=True
फ़्लैग है, तो cfg
को
साफ़ तौर पर सेट करना ज़रूरी है. ऐसा इसलिए किया जाता है, ताकि गलती से गलत कॉन्फ़िगरेशन के लिए टूल न बनाया जाए.
उदाहरण देखें
आम तौर पर, रनटाइम के दौरान ज़रूरी सोर्स, डिपेंडेंट लाइब्रेरी, और एक्ज़ीक्यूटेबल एक ही कॉन्फ़िगरेशन का इस्तेमाल कर सकते हैं.
ऐसे टूल जिन्हें बिल्ड के हिस्से के तौर पर चलाया जाता है, जैसे कि कंपाइलर या कोड जनरेटर, इन्हें एक्ज़ीक्यूट कॉन्फ़िगरेशन के लिए बनाया जाना चाहिए. ऐसे मामले में, एट्रिब्यूट में 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
नियमों में इस बारे में भी जानकारी होनी चाहिए कि coverage_common.instrumented_files_info
का इस्तेमाल करके बनाई गई InstrumentedFilesInfo
सेवा देने वाली कंपनी के कवरेज के लिए कौनसे एट्रिब्यूट काम के हैं.
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
में डालता है. हालांकि, इससे डिपेंडेंसी चेन में सभी नियमों के लिए, कवरेज कॉन्फ़िगरेशन की ज़रूरत नहीं होती.)
पुष्टि करने की कार्रवाइयां
कभी-कभी आपको बिल्ड के बारे में किसी चीज़ की पुष्टि करने की ज़रूरत होती है और पुष्टि करने के लिए ज़रूरी जानकारी सिर्फ़ आर्टफ़ैक्ट (सोर्स फ़ाइलें या जनरेट की गई फ़ाइलें) में उपलब्ध होती है. यह जानकारी आर्टफ़ैक्ट में होती है. इसलिए, विश्लेषण के समय नियम, इस पुष्टि को नहीं कर सकते, क्योंकि नियम फ़ाइलों को नहीं पढ़ सकते. इसके बजाय, कार्रवाइयों को लागू करने के समय इसकी पुष्टि करनी चाहिए. अगर पुष्टि नहीं हो पाती है, तो कार्रवाई नहीं हो पाएगी. इसलिए, बिल्ड भी ऐसा ही होगा.
की जा सकने वाली पुष्टि के उदाहरणों में, स्टैटिक विश्लेषण, लिंटिंग, डिपेंडेंसी और एक जैसे होने की जांच, और स्टाइल की जांच शामिल है.
पुष्टि करने की कार्रवाइयों से, उन कार्रवाइयों के कुछ हिस्सों को अलग-अलग कार्रवाइयों में ले जाने से भी परफ़ॉर्मेंस बेहतर हो सकती है जो आर्टफ़ैक्ट बनाने के लिए ज़रूरी नहीं हैं. उदाहरण के लिए, अगर कंपाइलेशन और लिंटिंग करने वाली एक कार्रवाई को कंपाइलेशन ऐक्शन और लिंटिंग ऐक्शन में अलग-अलग किया जा सकता है, तो लिंटिंग ऐक्शन को पुष्टि करने वाली कार्रवाई के तौर पर चलाया जा सकता है. साथ ही, इसे दूसरी कार्रवाइयों के साथ भी चलाया जा सकता है.
ये "पुष्टि करने वाली कार्रवाइयां" अक्सर ऐसा कोई नतीजा नहीं देतीं जिसका इस्तेमाल, बिल्ड में कहीं और किया जा सके. इसकी वजह यह है कि उन्हें सिर्फ़ अपने इनपुट के बारे में बताना होता है. इससे एक समस्या सामने आती है: अगर पुष्टि करने की कार्रवाई से ऐसा कुछ नहीं मिलता है जिसका इस्तेमाल बिल्ड में कहीं और किया जा रहा हो, तो नियम किस तरह से कार्रवाई पूरी करता है? पहले, यह तरीका था कि पुष्टि करने वाली कार्रवाई के आउटपुट में एक खाली फ़ाइल मौजूद हो. साथ ही, इस आउटपुट को आर्टिफ़िशियल तरीके से बिल्ड में मौजूद किसी दूसरी अहम कार्रवाई के इनपुट में जोड़ दिया जाए:
यह प्रोसेस काम करती है, क्योंकि कंपाइल ऐक्शन के चलते समय Baज़ल, पुष्टि करने वाला ऐक्शन हमेशा चलाएगा, लेकिन इसकी कुछ बड़ी कमियां भी हैं:
पुष्टि करने की कार्रवाई, बिल्ड के क्रिटिकल पाथ में होती है. Basel को लगता है कि कंपाइल ऐक्शन को चलाने के लिए, खाली आउटपुट ज़रूरी है. इसलिए, यह पहले पुष्टि करने की कार्रवाई चलाएगा, भले ही कंपाइल ऐक्शन, इनपुट को अनदेखा करेगा. इससे पैरलल प्रोसेसिंग कम हो जाती है और बिल्ड धीमे हो जाते हैं.
अगर कंपाइल कार्रवाई के बजाय बिल्ड में मौजूद अन्य कार्रवाइयां चल सकती हैं, तो उन कार्रवाइयों में पुष्टि करने वाली कार्रवाइयों के खाली आउटपुट भी जोड़ने होंगे. उदाहरण के लिए,
java_library
का सोर्स जार आउटपुट. यह तब भी एक समस्या होती है, जब कंपाइल कार्रवाई के बजाय नई कार्रवाइयां बाद में जोड़ी जाती हैं और पुष्टि करने वाला खाली आउटपुट गलती से हट जाता है.
इन समस्याओं का समाधान, वैलिडेशन आउटपुट ग्रुप का इस्तेमाल करना है.
पुष्टि करने के लिए आउटपुट ग्रुप
वैलिडेशन आउटपुट ग्रुप एक आउटपुट ग्रुप है, जिसे पुष्टि करने की कार्रवाइयों के इस्तेमाल न होने वाले आउटपुट को होल्ड करने के लिए डिज़ाइन किया गया है. इससे, उन्हें दूसरी कार्रवाइयों के इनपुट में आर्टिफ़िशियल तरीके से जोड़ने की ज़रूरत नहीं पड़ती.
यह ग्रुप खास है, क्योंकि इसके आउटपुट का अनुरोध हमेशा किया जाता है, चाहे --output_groups
फ़्लैग की वैल्यू कुछ भी हो. साथ ही, चाहे टारगेट किसी भी तरह से तय हो. उदाहरण के लिए, कमांड लाइन पर, डिपेंडेंसी के तौर पर या टारगेट के इंप्लिसिट आउटपुट के ज़रिए. ध्यान दें कि सामान्य कैश मेमोरी और इंक्रीमेंटलिटी अब भी लागू होती है: अगर पुष्टि करने की कार्रवाई के इनपुट में बदलाव नहीं हुआ है और पुष्टि करने की कार्रवाई पहले पूरी हो चुकी है, तो पुष्टि करने की कार्रवाई नहीं की जाएगी.
इस आउटपुट ग्रुप का इस्तेमाल करने के लिए, अब भी पुष्टि करने वाली कार्रवाइयों से कोई फ़ाइल आउटपुट होनी चाहिए. भले ही, वह फ़ाइल खाली हो. इसके लिए, कुछ ऐसे टूल को रैप करना पड़ सकता है जो आम तौर पर आउटपुट नहीं देते, ताकि फ़ाइल बनाई जा सके.
टारगेट की पुष्टि करने वाली कार्रवाइयां, इन तीन मामलों में नहीं चलाई जातीं:
- जब टारगेट, टूल के तौर पर निर्भर होता है
- जब टारगेट पर, किसी छिपी हुई डिपेंडेंसी के तौर पर निर्भर किया जाता है. उदाहरण के लिए, "_" से शुरू होने वाला एट्रिब्यूट
- जब टारगेट, होस्ट या exec कॉन्फ़िगरेशन में बनाया जाता है.
यह माना जाता है कि इन टारगेट के अपने अलग बिल्ड और टेस्ट हैं जिनसे पुष्टि में हुई किसी भी गड़बड़ी का पता चल जाएगा.
वैलिडेशन आउटपुट ग्रुप का इस्तेमाल करना
इस टूल की पुष्टि करने वाले आउटपुट ग्रुप का नाम _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
का इस्तेमाल करने के बजाय, नियमों में डिफ़ॉल्ट आउटपुट को शामिल करने के बजाय, रनफ़ाइल (अक्सरdata
) उपलब्ध कराने वाले एट्रिब्यूट केdefault_runfiles
में मर्ज होने चाहिए और दोनों को शामिल किया जाना चाहिए.DefaultInfo
सेrunfiles
को वापस लाने के लिए,DefaultInfo.data_runfiles
के बजायDefaultInfo.default_runfiles
का इस्तेमाल करें. आम तौर पर, ऐसा सिर्फ़ मौजूदा नियम और उसकी डिपेंडेंसी के बीच रनफ़ाइलों को मर्ज करने के लिए किया जाता है.
सेवा देने वाली पुरानी कंपनियों से माइग्रेट करना
अब तक, 'बेज़ल' प्रोवाइडर, 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
का इस्तेमाल करके, नई सेवा देने वाली कंपनी की मौजूदगी की जांच करें.लेगसी प्रोवाइडर को सभी नियमों से पूरी तरह हटाएं.