नियम

समस्या की शिकायत करें सोर्स देखें

नियम ऐसी कार्रवाइयों की सीरीज़ के बारे में बताता है जिन्हें बेज़ल, इनपुट का इस्तेमाल करके आउटपुट का सेट बनाने के लिए करते हैं. इन आउटपुट का सेट, नियम के लागू करने का फ़ंक्शन से दिए गए providers में बताया जाता है. उदाहरण के लिए, C++ बाइनरी नियम के तहत ये काम किए जा सकते हैं:

  1. .cpp सोर्स फ़ाइलों (इनपुट) का सेट लें.
  2. सोर्स फ़ाइलों पर g++ चलाएं (कार्रवाई).
  3. रनटाइम के दौरान उपलब्ध कराने के लिए, DefaultInfo प्रोवाइडर को एक्ज़ीक्यूटेबल आउटपुट और अन्य फ़ाइलों के साथ लौटाना.
  4. टारगेट और उसकी डिपेंडेंसी से इकट्ठा की गई C++ की खास जानकारी के साथ, CcInfo प्रोवाइडर को वापस भेजें.

बेज़ल के हिसाब से, इस नियम में g++ और स्टैंडर्ड C++ लाइब्रेरी भी इनपुट हैं. रूल राइटर के तौर पर, आपको किसी नियम के लिए उपयोगकर्ता से मिले इनपुट के साथ-साथ, कार्रवाइयां करने के लिए ज़रूरी सभी टूल और लाइब्रेरी को भी ध्यान में रखना चाहिए.

किसी भी नियम को बनाने या उसमें बदलाव करने से पहले, पक्का करें कि आपको Baज़र के बिल्ड फ़ेज़ के बारे में जानकारी हो. बिल्ड के तीन चरणों (लोडिंग, विश्लेषण, और एक्ज़ीक्यूशन) को समझना ज़रूरी है. नियमों और मैक्रो के बीच के अंतर को समझने के लिए, मैक्रो के बारे में जानना भी फ़ायदेमंद होता है. शुरू करने के लिए, पहले नियमों का ट्यूटोरियल देखें. इसके बाद, इस पेज को रेफ़रंस के तौर पर इस्तेमाल करें.

Basel में भी कुछ नियम बनाए गए हैं. ये नेटिव नियम, जैसे कि cc_library और java_binary, कुछ भाषाओं के लिए काम करते हैं. अपने नियम खुद तय करके, उन भाषाओं और टूल के लिए भी इससे मिलता-जुलता सपोर्ट जोड़ा जा सकता है जिन पर Basel मूल रूप से काम नहीं करता.

Basel ने Starlark भाषा का इस्तेमाल करके, नियम लिखने के लिए एक एक्सटेंशन मॉडल उपलब्ध कराया है. ये नियम .bzl फ़ाइलों में लिखे गए हैं, जिन्हें सीधे BUILD फ़ाइलों से लोड किया जा सकता है.

अपना नियम तय करते समय, आपको यह तय करना होगा कि वह किन एट्रिब्यूट के साथ काम करता है और किस तरह आउटपुट जनरेट करेगा.

विश्लेषण के चरण के दौरान नियम का implementation फ़ंक्शन, इसके सटीक व्यवहार के बारे में बताता है. यह फ़ंक्शन किसी बाहरी कमांड पर काम नहीं करता. इसके बजाय, यह ऐसी कार्रवाइयों को रजिस्टर करता है जिनका इस्तेमाल, बाद में नियमों के आउटपुट बनाने के लिए किया जाएगा. ऐसा ज़रूरत पड़ने पर, नियम लागू करने के चरण के दौरान किया जाएगा.

नियम बनाना

.bzl फ़ाइल में, नया नियम तय करने के लिए नियम फ़ंक्शन का इस्तेमाल करें और नतीजे को ग्लोबल वैरिएबल में स्टोर करें. 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),
        ...
    },
)

ये डिपेंडेंसी एट्रिब्यूट के उदाहरण हैं. इनपुट लेबल (जिन्हें attr.label_list, attr.label या attr.label_keyed_string_dict से परिभाषित किया गया है) के बारे में बताने वाला कोई भी एट्रिब्यूट, टारगेट और उन टारगेट के बीच किसी खास टाइप की डिपेंडेंसी के बारे में बताता है जिनके लेबल (या उससे जुड़े Label ऑब्जेक्ट) उस एट्रिब्यूट में टारगेट तय करते समय मौजूद होते हैं. इन लेबल के लिए, डेटा स्टोर करने की जगह और पाथ को तय किए गए टारगेट के हिसाब से हल किया जाता है.

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

इस उदाहरण में, other_target, my_target पर निर्भर करता है. इसलिए, other_target का विश्लेषण पहले किया जाता है. अगर टारगेट के डिपेंडेंसी ग्राफ़ में कोई साइकल है, तो यह एक गड़बड़ी है.

निजी एट्रिब्यूट और इंप्लिसिट डिपेंडेंसी

डिफ़ॉल्ट वैल्यू वाली डिपेंडेंसी एट्रिब्यूट, इंप्लिसिट डिपेंडेंसी बनाती है. इसका मतलब है कि यह टारगेट ग्राफ़ का हिस्सा है, जिसे उपयोगकर्ता ने BUILD फ़ाइल में नहीं बताया है. इंप्लिसिट डिपेंडेंसी किसी नियम और टूल (एक बिल्ड-टाइम डिपेंडेंसी, जैसे कि कंपाइलर) के बीच हार्ड-कोडिंग के संबंध के लिए काम की होती हैं, क्योंकि ज़्यादातर मामलों में उपयोगकर्ता यह तय करने में दिलचस्पी नहीं रखता है कि नियम किस टूल का इस्तेमाल करेगा. नियम के लागू करने के फ़ंक्शन के अंदर, इसे दूसरी डिपेंडेंसी की तरह ही माना जाता है.

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

टारगेट

डिपेंडेंसी को विश्लेषण के समय 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]

इस स्टाइल को इस्तेमाल करने की सलाह बिलकुल नहीं दी जाती. नियमों को इससे माइग्रेट नहीं किया जाना चाहिए.

Files

फ़ाइलों को File ऑब्जेक्ट के तौर पर दिखाया जाता है. विश्लेषण के दौरान Basel, फ़ाइल 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 पाएं". जब कोई कार्रवाई बनाई जाती है, तो Basel निर्देश तुरंत नहीं चलाता है. यह इसे डिपेंडेंसी के ग्राफ़ में रजिस्टर करता है, क्योंकि एक कार्रवाई दूसरी कार्रवाई के आउटपुट पर निर्भर हो सकती है. उदाहरण के लिए, 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, टार या कोई दूसरा संग्रह फ़ॉर्मैट).

कार्रवाइयों में अपने सभी इनपुट शामिल होने चाहिए. इस्तेमाल नहीं किए गए लिस्टिंग इनपुट की अनुमति है, लेकिन ग़लत हैं.

कार्रवाइयों के सभी आउटपुट बनाने चाहिए. वे दूसरी फ़ाइलें लिख सकते हैं, लेकिन जो भी चीज़ आउटपुट में शामिल नहीं है वह उपभोक्ताओं के लिए उपलब्ध नहीं होगी. सभी एलान किए गए आउटपुट, किसी कार्रवाई के साथ लिखे जाने चाहिए.

कार्रवाइयों की तुलना सिर्फ़ फ़ंक्शन से की जा सकती है: इन्हें सिर्फ़ दिए गए इनपुट पर निर्भर होना चाहिए. साथ ही, इन्हें कंप्यूटर की जानकारी, उपयोगकर्ता नाम, घड़ी, नेटवर्क या I/O डिवाइसों (इनपुट पढ़ने और आउटपुट लिखने को छोड़कर) को ऐक्सेस करने से बचना चाहिए. यह ज़रूरी है, क्योंकि आउटपुट को कैश मेमोरी में सेव करके, फिर से इस्तेमाल किया जाएगा.

डिपेंडेंसी का समाधान Basel की ओर से किया जाता है. इससे यह तय होगा कि कौनसी कार्रवाइयां की जाएंगी. अगर डिपेंडेंसी ग्राफ़ में कोई साइकल मौजूद है, तो यह एक गड़बड़ी है. कोई कार्रवाई बनाने से, इस बात की गारंटी नहीं मिलती कि उसे चलाया जाएगा. यह इस बात पर निर्भर करता है कि बिल्ड के लिए उसके आउटपुट की ज़रूरत है या नहीं.

सेवा देने वाले संगठन

सेवा देने वाली कंपनियां वह जानकारी होती है जिसे कोई नियम, इस पर निर्भर दूसरे नियमों को दिखाता है. इस डेटा में आउटपुट फ़ाइलें, लाइब्रेरी, किसी टूल की कमांड लाइन को पास करने के लिए पैरामीटर या ऐसी कोई भी अन्य चीज़ शामिल हो सकती है जिसके बारे में टारगेट के उपभोक्ताओं को पता होना चाहिए.

नियम को लागू करने का फ़ंक्शन, सेवा देने वाली कंपनियों को सिर्फ़ इंस्टेंट किए गए टारगेट के तुरंत डिपेंडेंसी से पहचान सकता है. इसलिए, नियमों को टारगेट के डिपेंडेंसी से किसी भी ऐसी जानकारी को फ़ॉरवर्ड करना चाहिए जिसकी जानकारी टारगेट के उपभोक्ताओं को पता हो. इसके लिए, आम तौर पर इस जानकारी को 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(
 ...
)

रनफ़ाइल की जगह

जब किसी एक्ज़ीक्यूटेबल टारगेट को 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 कमांड चलाया जाता है, तो कमांड को दिए गए टारगेट के कुछ आउटपुट के लिए, अनुरोध किया गया माना जाता है. Baज़र, सिर्फ़ इन अनुरोध की गई फ़ाइलों और उन फ़ाइलों को बनाता है जिन पर वे सीधे या अन्य तरीके से निर्भर होते हैं. (ऐक्शन ग्राफ़ के हिसाब से, Baज़र, सिर्फ़ वे कार्रवाइयां करता है जिन्हें अनुरोध की गई फ़ाइलों की ट्रांज़िटिव डिपेंडेंसी के तौर पर ऐक्सेस किया जा सकता है.)

डिफ़ॉल्ट आउटपुट के अलावा, किसी भी पहले से तय किए गए आउटपुट के लिए, कमांड लाइन पर साफ़ तौर पर अनुरोध किया जा सकता है. नियम आउटपुट एट्रिब्यूट के ज़रिए, पहले से तय किए गए आउटपुट के बारे में बता सकते हैं. ऐसे में, नियम को इंस्टैंशिएट करते समय उपयोगकर्ता, आउटपुट के लिए साफ़ तौर पर लेबल चुनता है. आउटपुट एट्रिब्यूट के लिए 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 का "कॉन्फ़िगरेशन" और ट्रांज़िशन का एक सिद्धांत है. सबसे ऊपर कमांड लाइन में अनुरोध किए गए टारगेट, "टारगेट" कॉन्फ़िगरेशन में बनाए जाते हैं. वहीं, एक्ज़ीक्यूशन प्लैटफ़ॉर्म पर चलने वाले टूल "एक्ज़िक" कॉन्फ़िगरेशन में बनाए जाते हैं. कॉन्फ़िगरेशन के आधार पर, नियम अलग-अलग कार्रवाइयां जनरेट कर सकते हैं. उदाहरण के लिए, कंपाइलर को पास किया जाने वाला सीपीयू आर्किटेक्चर बदलना. कुछ मामलों में, अलग-अलग कॉन्फ़िगरेशन के लिए एक ही लाइब्रेरी की ज़रूरत हो सकती है. अगर ऐसा होता है, तो इसका विश्लेषण किया जाएगा और हो सकता है कि इसे कई बार बनाया जाए.

डिफ़ॉल्ट रूप से, Basel, टारगेट की डिपेंडेंसी को उसी कॉन्फ़िगरेशन में बनाता है जिस पर कोई ट्रांज़िशन नहीं होता. जब डिपेंडेंसी एक ऐसा टूल होता है जो टारगेट बनाने में मदद करता है, तो इससे जुड़े एट्रिब्यूट को एक्ज़ेक्यूटिव कॉन्फ़िगरेशन के लिए ट्रांज़िशन के बारे में बताना चाहिए. इसी वजह से, टूल और इसकी सभी डिपेंडेंसी, एक्ज़ीक्यूशन प्लैटफ़ॉर्म के लिए तैयार होती हैं.

हर डिपेंडेंसी एट्रिब्यूट के लिए, cfg का इस्तेमाल किया जा सकता है. इससे यह तय किया जा सकता है कि डिपेंडेंसी को एक ही कॉन्फ़िगरेशन में बनाया जाना चाहिए या किसी एक्ज़ेक्यूटिव कॉन्फ़िगरेशन में बदलना चाहिए. अगर किसी डिपेंडेंसी एट्रिब्यूट में executable=True फ़्लैग है, तो cfg को साफ़ तौर पर सेट करना ज़रूरी है. यह इसलिए ज़रूरी है, ताकि गलती से गलत कॉन्फ़िगरेशन के लिए कोई टूल बना न दिया जाए. उदाहरण देखें

आम तौर पर, रनटाइम के दौरान जिन सोर्स, डिपेंडेंट लाइब्रेरी, और एक्ज़ीक्यूटेबल की ज़रूरत होती है वे एक ही कॉन्फ़िगरेशन का इस्तेमाल कर सकते हैं.

बिल्ड के हिस्से के रूप में एक्ज़ीक्यूट किए जाने वाले टूल (जैसे कि कंपाइलर या कोड जनरेटर) किसी exec कॉन्फ़िगरेशन के लिए बनाए जाने चाहिए. ऐसे मामले में, एट्रिब्यूट में cfg="exec" के बारे में बताएं.

ऐसा नहीं होने पर, रनटाइम के दौरान इस्तेमाल किए जाने वाले एक्ज़ीक्यूटेबल (जैसे कि टेस्ट के हिस्से) को टारगेट कॉन्फ़िगरेशन के लिए बनाया जाना चाहिए. ऐसे मामले में, एट्रिब्यूट में cfg="target" के बारे में बताएं.

cfg="target" असल में कुछ भी नहीं करता: यह सिर्फ़ नियम बनाने वाले डिज़ाइनरों को उनके इरादों के बारे में बताने में मदद करने के लिए एक सुविधा है. जब executable=False, जिसका मतलब है कि cfg ज़रूरी नहीं है, तब इसे सिर्फ़ तब सेट करें, जब यह वाकई में पढ़ने लायक हो.

उपयोगकर्ता के तय किए गए ट्रांज़िशन का इस्तेमाल करने के लिए भी cfg=my_transition का इस्तेमाल किया जा सकता है. इससे नियम लिखने वाले लोग, कॉन्फ़िगरेशन में काफ़ी बदलाव कर सकते हैं. साथ ही, बिल्ड ग्राफ़ को बड़ा और समझ में आने लायक बनाने की एक खामियां भी हो सकती हैं.

ध्यान दें: पहले, Basel के पास एक्ज़ीक्यूशन प्लैटफ़ॉर्म का कॉन्सेप्ट नहीं था. इसके बजाय, सभी बिल्ड ऐक्शन को होस्ट मशीन पर चलाया जाता था. इस वजह से, एक "होस्ट" कॉन्फ़िगरेशन और "होस्ट" ट्रांज़िशन होता है. इसका इस्तेमाल, होस्ट कॉन्फ़िगरेशन में डिपेंडेंसी बनाने के लिए किया जा सकता है. कई नियम अब भी अपने टूल के लिए "होस्ट" ट्रांज़िशन का इस्तेमाल करते हैं. हालांकि, इस ट्रांज़िशन का इस्तेमाल अब नहीं किया जा रहा है. जहां भी संभव हो वहां "exec" ट्रांज़िशन का इस्तेमाल करने के लिए इसे माइग्रेट किया जा रहा है.

"होस्ट" और "exec" कॉन्फ़िगरेशन के बीच कई अंतर हैं:

  • "host" टर्मिनल है, "exec" नहीं: "होस्ट" कॉन्फ़िगरेशन में डिपेंडेंसी होने के बाद, और ट्रांज़िशन की अनुमति नहीं होती. "exec" कॉन्फ़िगरेशन में शामिल होने के बाद, कॉन्फ़िगरेशन में आगे भी बदलाव करना जारी रखा जा सकता है.
  • "होस्ट" मोनोलिथिक है, "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 का इस्तेमाल करें.

आम तौर पर, रनफ़ाइल ट्री में किसी फ़ाइल का रिलेटिव पाथ, सोर्स ट्री या जनरेट किए गए आउटपुट ट्री में उस फ़ाइल के रिलेटिव पाथ के बराबर होता है. अगर किसी वजह से ये अलग-अलग होने चाहिए, तो आपके पास 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ज़ल, पुष्टि करने वाली कार्रवाई हमेशा चलाएगा, लेकिन इसकी कुछ बड़ी कमियां भी हैं:

  1. पुष्टि करने की कार्रवाई, बिल्ड के अहम पाथ में होती है. Basel को लगता है कि कंपाइल ऐक्शन को चलाने के लिए, खाली आउटपुट ज़रूरी है. इसलिए, यह पहले पुष्टि करने की कार्रवाई चलाएगा, भले ही कंपाइल ऐक्शन, इनपुट को अनदेखा करेगा. यह पैरलिज़्म को कम करता है और बिल्ड को धीमा करता है.

  2. अगर कंपाइल कार्रवाई के बजाय बिल्ड में मौजूद अन्य कार्रवाइयां चल सकती हैं, तो उन कार्रवाइयों में पुष्टि करने वाली कार्रवाइयों के खाली आउटपुट भी जोड़ने होंगे. उदाहरण के लिए, 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 या किसी दूसरी कार्रवाई के इनपुट में नहीं जोड़ा गया है. इस नियम के टाइप के टारगेट के लिए, पुष्टि करने की कार्रवाई अब भी तब भी चलती रहेगी, जब तक कि टारगेट, लेबल पर निर्भर हो या टारगेट के इंप्लिसिट आउटपुट सीधे तौर पर या किसी अन्य तरीके से निर्भर हों.

आम तौर पर, यह ज़रूरी होता है कि पुष्टि करने की कार्रवाइयों के आउटपुट सिर्फ़ पुष्टि करने वाले आउटपुट ग्रुप में जाएं. उन्हें दूसरी कार्रवाइयों के इनपुट में नहीं जोड़ा जाता, क्योंकि इससे एक साथ काम करने की क्षमता में कमी आ सकती है. हालांकि ध्यान दें कि फ़िलहाल Baज़ल के पास, इसे लागू करने के लिए कोई खास जांच नहीं है. इसलिए, आपको यह जांच करनी चाहिए कि 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.default_runfiles का इस्तेमाल करें, DefaultInfo.data_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 सिंबल के साथ पास किया जाता है. माइग्रेट करते समय स्ट्रिंग से सिंबल को बदलना न भूलें. जटिल या बड़े नियम सेट के लिए, जहां सभी नियमों को अपने-आप अपडेट करना मुश्किल होता है, उनके लिए इन चरणों के क्रम का पालन करने पर काम आसान हो सकता है:

  1. ऊपर दिए गए सिंटैक्स का इस्तेमाल करके, लेगसी प्रोवाइडर और मॉडर्न, दोनों तरह की सेवाएं देने के लिए, लेगसी प्रोवाइडर बनाने के नियमों में बदलाव करें. जिन नियमों में, लेगसी प्रोवाइडर को लौटाने का एलान किया जाता है उनके लिए, एलान वाले फ़ॉर्म को अपडेट करें. ऐसा करने पर, लेगसी और मॉडर्न, दोनों तरह की सेवाएं देने वाली कंपनियों को शामिल किया जा सकेगा.

  2. उन नियमों में बदलाव करें जो नए वर्शन का इस्तेमाल करने के बजाय, लेगसी प्रोवाइडर का इस्तेमाल करते हैं. अगर किसी एट्रिब्यूट की जानकारी के लिए, लेगसी प्रोवाइडर की ज़रूरत होती है, तो उसे भी अपडेट करें. ऐसा करने के लिए, मॉडर्न प्रोवाइडर की ज़रूरत होती है. इसके अलावा, पहले चरण में इस काम को किया जा सकता है. इसके लिए यह पक्का करें कि उपभोक्ता, सेवा देने वाली किसी कंपनी को स्वीकार कर सकते हैं या उसे ज़रूरी बना सकते हैं: hasattr(target, 'foo') का इस्तेमाल करके, सेवा देने वाली नई कंपनी की मौजूदगी की जांच करें या FooInfo in target का इस्तेमाल करके, नई कंपनी की मौजूदगी की जांच करें.

  3. लेगसी प्रोवाइडर को सभी नियमों से पूरी तरह हटाएं.