नियम, कार्रवाइयों की एक सीरीज़ तय करता है. इन कार्रवाइयों को Bazel, इनपुट पर करता है, ताकि आउटपुट का एक सेट तैयार किया जा सके. इन आउटपुट का रेफ़रंस, नियम के लागू करने वाले फ़ंक्शन से मिले प्रोवाइडर में दिया जाता है. उदाहरण के लिए, C++ के बाइनरी नियम में ये काम किए जा सकते हैं:
.cpp
सोर्स फ़ाइलों (इनपुट) का सेट लें.- सोर्स फ़ाइलों (कार्रवाई) पर
g++
चलाएं. - रनटाइम के दौरान उपलब्ध कराने के लिए,
DefaultInfo
प्रोवाइडर को, रन किए जा सकने वाले आउटपुट और अन्य फ़ाइलों के साथ दिखाएं. - टारगेट और उसकी डिपेंडेंसी से इकट्ठा की गई C++ की खास जानकारी के साथ,
CcInfo
प्रोवाइडर को वापस भेजें.
बेज़ल के हिसाब से, इस नियम में g++
और स्टैंडर्ड C++ लाइब्रेरी भी इनपुट हैं. रूल राइटर के तौर पर, आपको किसी नियम के लिए उपयोगकर्ता से मिले इनपुट के साथ-साथ, कार्रवाइयां करने के लिए ज़रूरी सभी टूल और लाइब्रेरी को भी ध्यान में रखना चाहिए.
कोई नियम बनाने या उसमें बदलाव करने से पहले, पक्का करें कि आपको Bazel के बिल्ड फ़ेज़ के बारे में पता हो. बिल्ड के तीन चरणों (लोडिंग, विश्लेषण, और लागू करना) को समझना ज़रूरी है. नियमों और मैक्रो के बीच के अंतर को समझने के लिए, मैक्रो के बारे में जानना भी फ़ायदेमंद है. शुरू करने के लिए, सबसे पहले नियमों का ट्यूटोरियल देखें. इसके बाद, इस पेज को रेफ़रंस के तौर पर इस्तेमाल करें.
Basel में भी कुछ नियम बनाए गए हैं. ये नेटिव नियम, जैसे कि
cc_library
और java_binary
, कुछ भाषाओं के लिए काम करते हैं.
अपने नियम तय करके, उन भाषाओं और टूल के लिए भी इसी तरह की सहायता जोड़ी जा सकती है जिनके लिए Bazel में पहले से सुविधा उपलब्ध नहीं है.
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 मैक्रो कहा जाता है.
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
फ़ाइल में नहीं बताता. इसलिए, यह वैल्यू अपने-आप लागू होती है. किसी नियम और टूल (बिल्ड टाइम डिपेंडेंसी, जैसे कि कंपाइलर) के बीच के संबंध को हार्ड-कोड करने के लिए, इनपुट डिपेंडेंसी काम की होती हैं. ऐसा इसलिए, क्योंकि ज़्यादातर समय में उपयोगकर्ता को यह बताने में दिलचस्पी नहीं होती कि नियम किस टूल का इस्तेमाल करता है. नियम को लागू करने वाले फ़ंक्शन में, इसे अन्य डिपेंडेंसी की तरह ही माना जाता है.
अगर आपको उपयोगकर्ता को वह वैल्यू बदलने की अनुमति दिए बिना, इंप्लिसिट डिपेंडेंसी देना है, तो एट्रिब्यूट को private बनाया जा सकता है. इसके लिए, अंडरस्कोर (_
) से शुरू होने वाला नाम दें. निजी एट्रिब्यूट की डिफ़ॉल्ट वैल्यू होनी चाहिए. आम तौर पर, इंप्लिसिट डिपेंडेंसी के लिए निजी एट्रिब्यूट का इस्तेमाल करना सही होता है.
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
ऑब्जेक्ट से दिखाया जाता है. हालांकि, विश्लेषण के दौरान 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
डिफ़ॉल्ट रूप से पहले से तय किए गए आउटपुट पर सेट होता है. आम तौर पर, वे आउटपुट एट्रिब्यूट से बनाए जाते हैं.
कार्रवाइयां करने वाले नियमों को डिफ़ॉल्ट आउटपुट देने चाहिए. भले ही, उन आउटपुट का सीधे तौर पर इस्तेमाल न किया जा रहा हो. जिन कार्रवाइयों को अनुरोध किए गए आउटपुट के ग्राफ़ में शामिल नहीं किया गया है उन्हें हटा दिया जाता है. अगर आउटपुट का इस्तेमाल सिर्फ़ टारगेट के उपभोक्ता करते हैं, तो टारगेट के अलग-अलग होने पर वे कार्रवाइयां नहीं की जाएंगी. इससे, डिबग करने की प्रोसेस ज़्यादा मुश्किल हो जाती है. ऐसा इसलिए, क्योंकि सिर्फ़ गड़बड़ी वाले टारगेट को फिर से बनाने पर, गड़बड़ी दोबारा नहीं दिखेगी.
रनफ़ाइल
रनफ़ाइलें, फ़ाइलों का एक सेट होता है. इसका इस्तेमाल टारगेट, रनटाइम के दौरान करता है, न कि बिल्डटाइम के दौरान. लागू करने की प्रोसेस के दौरान, Basel एक डायरेक्ट्री ट्री बनाता है. इसमें रनफ़ाइल पर ले जाने वाले सिमलिंक होते हैं. इससे बाइनरी के लिए एनवायरमेंट तैयार होता है, ताकि वह रनटाइम के दौरान रनफ़ाइलों को ऐक्सेस कर सके.
नियम बनाते समय, रनफ़ाइलों को मैन्युअल तरीके से जोड़ा जा सकता है.
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
निर्देशों से चालू किया जाएगा. बैजेल को यह बताने के लिए कि नियम के किस आउटपुट को इस एक्ज़ीक्यूटेबल के रूप में इस्तेमाल करना है, उसे लौटाए गए 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(
...
)
Runfiles की जगह
जब किसी एक्सीक्यूटेबल टारगेट को 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 नाम वाले बाइनरी, ऐसा नहीं कर सकते. इसे कम करने के लिए, हर बाइनरी को एनवायरमेंट या कमांड लाइन/फ़्लैग का इस्तेमाल करके, अपने रनफ़ाइल रूट को पैरामीटर के तौर पर स्वीकार करने का तरीका देना चाहिए. इससे बाइनरी कॉल करने वाली बाइनरी को सही कैननिकल रनफ़ाइल रूट
भेज सकती हैं. अगर यह सेट नहीं है, तो बाइनरी यह अनुमान लगा सकती है कि इसे सबसे पहले कॉल किया गया था और वह आस-पास मौजूद runfiles डायरेक्ट्री खोज सकती है.
उन्नत विषय
आउटपुट फ़ाइलों का अनुरोध करना
एक टारगेट में कई आउटपुट फ़ाइलें हो सकती हैं. 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++ बाइनरी बनानी है. बिल्ड मुश्किल हो सकता है और इसमें कई चरण शामिल हो सकते हैं. कुछ इंटरमीडिएट बाइनरी, जैसे कि कंपाइलर और कोड जनरेटर को एक्सीक्यूशन प्लैटफ़ॉर्म पर चलाना होता है. यह प्लैटफ़ॉर्म आपका होस्ट या रिमोट एक्सीक्यूटर हो सकता है. टारगेट आर्किटेक्चर के लिए, फ़ाइनल आउटपुट जैसी कुछ बाइनरी बनाई जानी चाहिए.
इसी वजह से, Basel का "कॉन्फ़िगरेशन" और ट्रांज़िशन का एक सिद्धांत है. सबसे ऊपर मौजूद टारगेट (कमांड लाइन पर अनुरोध किए गए टारगेट), "टारगेट" कॉन्फ़िगरेशन में बनाए जाते हैं. वहीं, ऐसे टूल जिन्हें एक्सीक्यूशन प्लैटफ़ॉर्म पर चलाया जाना चाहिए उन्हें "exec" कॉन्फ़िगरेशन में बनाया जाता है. कॉन्फ़िगरेशन के आधार पर, नियम अलग-अलग कार्रवाइयां जनरेट कर सकते हैं. उदाहरण के लिए, कंपाइलर को पास किए गए सीपीयू आर्किटेक्चर को बदलने के लिए. कुछ मामलों में, अलग-अलग कॉन्फ़िगरेशन के लिए एक ही लाइब्रेरी की ज़रूरत पड़ सकती है. ऐसा होने पर, इसका विश्लेषण किया जाएगा और इसे कई बार बनाया जा सकता है.
डिफ़ॉल्ट रूप से, Bazel किसी टारगेट की डिपेंडेंसी को उसी कॉन्फ़िगरेशन में बनाता है जिसमें टारगेट बना है. दूसरे शब्दों में, ट्रांज़िशन के बिना. जब डिपेंडेंसी एक ऐसा टूल होता है जो टारगेट बनाने में मदद करता है, तो इससे जुड़े एट्रिब्यूट को एक्ज़ेक्यूटिव कॉन्फ़िगरेशन के लिए ट्रांज़िशन के बारे में बताना चाहिए. इससे, टूल और उसकी सभी डिपेंडेंसी, लागू करने वाले प्लैटफ़ॉर्म के लिए बन जाती हैं.
हर डिपेंडेंसी एट्रिब्यूट के लिए, cfg
का इस्तेमाल करके यह तय किया जा सकता है कि डिपेंडेंसी एक ही कॉन्फ़िगरेशन में बननी चाहिए या किसी एक्सेक्यूट कॉन्फ़िगरेशन में ट्रांज़िशन होनी चाहिए.
अगर किसी डिपेंडेंसी एट्रिब्यूट में फ़्लैग executable=True
है, तो cfg
को साफ़ तौर पर सेट किया जाना चाहिए. ऐसा इसलिए किया जाता है, ताकि गलती से गलत कॉन्फ़िगरेशन के लिए टूल न बनाया जाए.
उदाहरण देखें
आम तौर पर, रनटाइम के दौरान ज़रूरी सोर्स, डिपेंडेंट लाइब्रेरी, और एक्ज़ीक्यूटेबल एक ही कॉन्फ़िगरेशन का इस्तेमाल कर सकते हैं.
बिल्ड के हिस्से के रूप में एक्ज़ीक्यूट किए जाने वाले टूल (जैसे कि कंपाइलर या कोड जनरेटर)
किसी exec कॉन्फ़िगरेशन के लिए बनाए जाने चाहिए. इस मामले में, एट्रिब्यूट में cfg="exec"
डालें.
ऐसा नहीं होने पर, रनटाइम के दौरान इस्तेमाल किए जाने वाले एक्ज़ीक्यूटेबल (जैसे कि टेस्ट के हिस्से) को टारगेट कॉन्फ़िगरेशन के लिए बनाया जाना चाहिए. इस मामले में, एट्रिब्यूट में cfg="target"
डालें.
cfg="target"
असल में कुछ नहीं करता: यह सिर्फ़ एक सुविधा है, ताकि नियम बनाने वाले अपने मकसद के बारे में साफ़ तौर पर बता सकें. जब executable=False
,
इसका मतलब है कि cfg
ज़रूरी नहीं है, तो इसे सिर्फ़ तब सेट करें, जब इससे टेक्स्ट को पढ़ने में ज़्यादा मदद मिलती हो.
उपयोगकर्ता के तय किए गए ट्रांज़िशन का इस्तेमाल करने के लिए भी cfg=my_transition
का इस्तेमाल किया जा सकता है. इससे नियम लिखने वाले लोग, कॉन्फ़िगरेशन में काफ़ी बदलाव कर सकते हैं. साथ ही, बिल्ड ग्राफ़ को बड़ा और समझ में आने लायक बनाने की एक खामियां भी हो सकती हैं.
ध्यान दें: पहले, Bazel में एक्ज़ीक्यूशन प्लैटफ़ॉर्म का कॉन्सेप्ट नहीं था. इसके बजाय, सभी बिल्ड ऐक्शन को होस्ट मशीन पर चलाया जाता था. इस वजह से, एक "होस्ट" कॉन्फ़िगरेशन और एक "होस्ट" ट्रांज़िशन होता है. इसका इस्तेमाल, होस्ट कॉन्फ़िगरेशन में डिपेंडेंसी बनाने के लिए किया जा सकता है. कई नियम अब भी अपने टूल के लिए "होस्ट" ट्रांज़िशन का इस्तेमाल करते हैं. हालांकि, फ़िलहाल इसे बंद कर दिया गया है और जहां भी हो सके वहां "एग्ज़ीक्यूट" ट्रांज़िशन का इस्तेमाल करने के लिए माइग्रेट किया जा रहा है.
"होस्ट" और "exec" कॉन्फ़िगरेशन में कई अंतर हैं:
- "होस्ट" टर्मिनल है, "exec" नहीं: "होस्ट" कॉन्फ़िगरेशन में डिपेंडेंसी होने के बाद, और ट्रांज़िशन की अनुमति नहीं होती. "एक्ज़िक" कॉन्फ़िगरेशन चालू करने के बाद, कॉन्फ़िगरेशन में आगे भी बदलाव करना जारी रखा जा सकता है.
- "होस्ट" मोनोलिथिक है, "एग्ज़ीक्यूट" नहीं: सिर्फ़ एक "होस्ट" कॉन्फ़िगरेशन होता है, लेकिन हर एग्ज़ीक्यूशन प्लैटफ़ॉर्म के लिए अलग "एग्ज़ीक्यूट" कॉन्फ़िगरेशन हो सकता है.
- "host" के तौर पर यह माना गया है कि आप Basel की तरह की मशीन पर या काफ़ी हद तक उससे मिलती-जुलती मशीन पर टूल चलाते हैं. अब ऐसा नहीं है: अपनी लोकल मशीन या किसी रिमोट एक्ज़ीक्यूटर पर, बिल्ड ऐक्शन चलाए जा सकते हैं. इस बात की कोई गारंटी नहीं है कि रिमोट एक्ज़ीक्यूटर का सीपीयू और ओएस, आपकी लोकल मशीन के सीपीयू और ओएस जैसा ही होगा.
"एक्ज़ीक्यूटिव" और "होस्ट", दोनों कॉन्फ़िगरेशन में एक जैसे बदलाव लागू होते हैं. उदाहरण के लिए, --compilation_mode
को --host_compilation_mode
से सेट करें, --cpu
को --host_cpu
से सेट करें वगैरह. अंतर यह है कि "होस्ट" कॉन्फ़िगरेशन, सभी अन्य फ़्लैग की डिफ़ॉल्ट वैल्यू से शुरू होता है, जबकि "एग्ज़ीक्यूट" कॉन्फ़िगरेशन, टारगेट कॉन्फ़िगरेशन के आधार पर फ़्लैग की मौजूदा वैल्यू से शुरू होता है.
कॉन्फ़िगरेशन फ़्रैगमेंट
नियम, 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
...
)
ctx.fragments
सिर्फ़ टारगेट कॉन्फ़िगरेशन के लिए कॉन्फ़िगरेशन फ़्रैगमेंट उपलब्ध कराता है. अगर आपको होस्ट कॉन्फ़िगरेशन के लिए फ़्रैगमेंट ऐक्सेस करने हैं, तो इसके बजाय ctx.host_fragments
का इस्तेमाल करें.
Runfiles सिमलिंक
आम तौर पर, runfiles ट्री में किसी फ़ाइल का रिलेटिव पाथ, सोर्स ट्री या जनरेट किए गए आउटपुट ट्री में उस फ़ाइल के रिलेटिव पाथ जैसा ही होता है. अगर किसी वजह से इनके लिए अलग-अलग वैल्यू देनी हैं, तो 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"
पर सेट नहीं किया जाता है). (यह तरीका सही नहीं है, क्योंकि यह source_attributes
की जगह srcs
जैसे एट्रिब्यूट को dependency_attributes
में शामिल करता है. हालांकि, इससे डिपेंडेंसी चेन के सभी नियमों के लिए, कवरेज कॉन्फ़िगरेशन की ज़रूरत नहीं पड़ती.)
पुष्टि करने से जुड़ी कार्रवाइयां
कभी-कभी आपको बिल्ड के बारे में कुछ की पुष्टि करनी पड़ती है. पुष्टि करने के लिए ज़रूरी जानकारी, सिर्फ़ आर्टफ़ैक्ट (सोर्स फ़ाइलें या जनरेट की गई फ़ाइलें) में उपलब्ध होती है. यह जानकारी आर्टफ़ैक्ट में होती है. इसलिए, विश्लेषण के समय नियम, इस पुष्टि को नहीं कर सकते, क्योंकि नियम फ़ाइलों को नहीं पढ़ सकते. इसके बजाय, कार्रवाइयों को लागू करने के समय इसकी पुष्टि करनी चाहिए. पुष्टि न होने पर, कार्रवाई पूरी नहीं होगी और इसलिए बिल्ड भी पूरा नहीं होगा.
पुष्टि करने के लिए, स्टैटिक विश्लेषण, लिंटिंग, डिपेंडेंसी और कंसिस्टेंसी की जांच, और स्टाइल की जांच जैसी प्रोसेस इस्तेमाल की जा सकती हैं.
पुष्टि करने वाली कार्रवाइयों से, आर्टफ़ैक्ट बनाने के लिए ज़रूरी नहीं होने वाली कार्रवाइयों के हिस्सों को अलग-अलग कार्रवाइयों में ले जाकर, बिल्ड की परफ़ॉर्मेंस को बेहतर बनाने में भी मदद मिल सकती है. उदाहरण के लिए, अगर किसी एक ऐक्शन में कोड को कंपाइल और लिंट करने की सुविधा को, कंपाइल करने की सुविधा और लिंट करने की सुविधा में अलग-अलग किया जा सकता है, तो लिंट करने की सुविधा को पुष्टि करने की सुविधा के तौर पर चलाया जा सकता है. साथ ही, इसे अन्य ऐक्शन के साथ चलाया जा सकता है.
ये "पुष्टि करने वाली कार्रवाइयां" अक्सर ऐसा कोई नतीजा नहीं देतीं जिसका इस्तेमाल, बिल्ड में कहीं और किया जा सके. इसकी वजह यह है कि उन्हें सिर्फ़ अपने इनपुट के बारे में बताना होता है. हालांकि, इससे एक समस्या आती है: अगर पुष्टि करने वाली कार्रवाई से कोई ऐसी वैल्यू नहीं मिलती जिसका इस्तेमाल बिल्ड में कहीं और किया जाता है, तो नियम से कार्रवाई कैसे चलाई जाती है? पहले, यह तरीका था कि पुष्टि करने वाली कार्रवाई के आउटपुट में एक खाली फ़ाइल मौजूद हो. साथ ही, इस आउटपुट को आर्टिफ़िशियल तरीके से बिल्ड में मौजूद किसी दूसरी अहम कार्रवाई के इनपुट में जोड़ दिया जाए:
यह प्रोसेस काम करती है, क्योंकि कंपाइल ऐक्शन के चलते समय Baज़ल, पुष्टि करने वाला ऐक्शन हमेशा चलाएगा, लेकिन इसकी कुछ बड़ी कमियां भी हैं:
पुष्टि करने की कार्रवाई, बिल्ड के अहम पाथ में होती है. Bazel को लगता है कि कंपाइल ऐक्शन को चलाने के लिए खाली आउटपुट ज़रूरी है. इसलिए, यह पहले पुष्टि करने की कार्रवाई चलाएगा. भले ही, कंपाइल ऐक्शन इनपुट को अनदेखा कर देगा. इससे पैरलल प्रोसेसिंग कम हो जाती है और बिल्ड धीमे हो जाते हैं.
अगर कंपाइल करने की कार्रवाई के बजाय, बिल्ड में अन्य कार्रवाइयां चल सकती हैं, तो पुष्टि करने की कार्रवाइयों के खाली आउटपुट को उन कार्रवाइयों में भी जोड़ना होगा. उदाहरण के लिए,
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
पैरामीटर में साफ़ तौर पर पास करें.
Runfiles की ऐसी सुविधाएं जिनसे बचना चाहिए
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.data_runfiles
के बजायDefaultInfo.default_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
का इस्तेमाल करके, नई कंपनी की मौजूदगी की जांच करें.लेगसी प्रोवाइडर को सभी नियमों से पूरी तरह हटाएं.