नियम

किसी समस्या की शिकायत करना सोर्स देखना Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

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

कुछ नियम, Bazel में पहले से मौजूद होते हैं. genrule और filegroup जैसे नेटिव नियम, कुछ मुख्य सहायता देते हैं. अपने नियम तय करके, उन भाषाओं और टूल के लिए सहायता जोड़ी जा सकती है जिनके साथ Bazel काम नहीं करता.

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

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

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

नियम बनाना

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

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

इससे example_library नाम के नियम का टाइप तय होता है.

rule को कॉल करने पर यह भी बताना ज़रूरी है कि नियम, executable = True के साथ executable आउटपुट बनाता है या खास तौर पर 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),
        ...
    },
)

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

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

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

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

निजी एट्रिब्यूट और लागू होने वाली डिपेंडेंसी

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

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

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

इस उदाहरण में, example_library टाइप के हर टारगेट की, कंपाइलर //tools:example_compiler पर आश्रितता होती है. इससे, example_library के लागू करने वाले फ़ंक्शन को ऐसी कार्रवाइयां जनरेट करने की अनुमति मिलती है जो कंपाइलर को ट्रिगर करती हैं. भले ही, उपयोगकर्ता ने इनपुट के तौर पर उसका लेबल पास न किया हो. _compiler एक निजी एट्रिब्यूट है. इसलिए, इस तरह के नियम के सभी टारगेट में ctx.attr._compiler हमेशा //tools:example_compiler पर ले जाएगा. इसके अलावा, एट्रिब्यूट का नाम अंडरस्कोर के बिना compiler रखा जा सकता है और डिफ़ॉल्ट वैल्यू को बनाए रखा जा सकता है. इससे उपयोगकर्ताओं को ज़रूरत पड़ने पर, किसी दूसरे कंपाइलर का इस्तेमाल करने की अनुमति मिलती है. हालांकि, इसके लिए उन्हें कंपाइलर के लेबल के बारे में जानने की ज़रूरत नहीं होती.

आम तौर पर, इनपुट के तौर पर इस्तेमाल होने वाले टूल के लिए, लागू किए गए नियम के साथ मौजूद रिपॉज़िटरी का इस्तेमाल किया जाता है. अगर टूल, एक्सीक्यूशन प्लैटफ़ॉर्म या किसी दूसरी रिपॉज़िटरी से आता है, तो नियम को उस टूल को टूलचेन से हासिल करना चाहिए.

आउटपुट एट्रिब्यूट

आउटपुट एट्रिब्यूट, जैसे कि attr.output और attr.output_list, उस आउटपुट फ़ाइल के बारे में बताते हैं जिसे टारगेट जनरेट करता है. ये, डिपेंडेंसी एट्रिब्यूट से दो तरीकों से अलग होते हैं:

  • ये किसी दूसरी जगह तय किए गए टारगेट का रेफ़रंस देने के बजाय, आउटपुट फ़ाइल के टारगेट तय करते हैं.
  • आउटपुट फ़ाइल के टारगेट, इंस्टैंशिएट किए गए नियम के टारगेट पर निर्भर करते हैं, न कि इसके उलट.

आम तौर पर, आउटपुट एट्रिब्यूट का इस्तेमाल सिर्फ़ तब किया जाता है, जब किसी नियम को उपयोगकर्ता के तय किए गए नामों के साथ आउटपुट बनाने की ज़रूरत होती है. ये नाम, टारगेट के नाम पर आधारित नहीं हो सकते. अगर किसी नियम में एक आउटपुट एट्रिब्यूट है, तो आम तौर पर उसका नाम out या outs होता है.

पहले से तय किए गए आउटपुट बनाने के लिए, आउटपुट एट्रिब्यूट का इस्तेमाल करना सबसे सही तरीका है. इन पर खास तौर पर भरोसा किया जा सकता है या कमांड लाइन पर इनका अनुरोध किया जा सकता है.

लागू करने का फ़ंक्शन

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

नियम लागू करने वाले फ़ंक्शन आम तौर पर निजी होते हैं. इनका नाम शुरू में अंडरस्कोर होता है. आम तौर पर, इनका नाम उनके नियम के नाम जैसा ही होता है, लेकिन आखिर में _impl होता है.

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

टारगेट

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

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]

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

फ़ाइलें

फ़ाइलों को File ऑब्जेक्ट से दिखाया जाता है. विश्लेषण के दौरान, Bazel फ़ाइल I/O का इस्तेमाल नहीं करता. इसलिए, इन ऑब्जेक्ट का इस्तेमाल सीधे तौर पर फ़ाइल का कॉन्टेंट पढ़ने या उसमें लिखने के लिए नहीं किया जा सकता. इसके बजाय, इन्हें ऐक्शन ग्राफ़ के हिस्सों को बनाने के लिए, ऐक्शन उत्सर्जित करने वाले फ़ंक्शन (ctx.actions देखें) को पास किया जाता है.

File, सोर्स फ़ाइल या जनरेट की गई फ़ाइल हो सकती है. जनरेट की गई हर फ़ाइल, सिर्फ़ एक कार्रवाई का आउटपुट होनी चाहिए. सोर्स फ़ाइलें किसी भी कार्रवाई का आउटपुट नहीं हो सकतीं.

हर डिपेंडेंसी एट्रिब्यूट के लिए, ctx.files से जुड़े फ़ील्ड में, उस एट्रिब्यूट का इस्तेमाल करने वाली सभी डिपेंडेंसी के डिफ़ॉल्ट आउटपुट की सूची होती है:

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file में, डिपेंडेंसी एट्रिब्यूट के लिए एक File या None होता है. इन एट्रिब्यूट के स्पेसिफ़िकेशन, allow_single_file = True को सेट करते हैं. ctx.executable, ctx.file की तरह ही काम करता है. हालांकि, इसमें सिर्फ़ उन डिपेंडेंसी एट्रिब्यूट के लिए फ़ील्ड होते हैं जिनके स्पेसिफ़िकेशन में executable = True सेट होता है.

आउटपुट का एलान करना

विश्लेषण के चरण के दौरान, नियम लागू करने वाला फ़ंक्शन आउटपुट बना सकता है. लोडिंग के दौरान सभी लेबल की जानकारी होनी चाहिए. इसलिए, इन अतिरिक्त आउटपुट में कोई लेबल नहीं होता. आउटपुट के लिए File ऑब्जेक्ट, ctx.actions.declare_file और ctx.actions.declare_directory का इस्तेमाल करके बनाए जा सकते हैं. आम तौर पर, आउटपुट के नाम टारगेट के नाम पर आधारित होते हैं, ctx.label.name:

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

पहले से तय किए गए आउटपुट के लिए, File ऑब्जेक्ट को ctx.outputs के संबंधित फ़ील्ड से वापस पाया जा सकता है. जैसे, आउटपुट एट्रिब्यूट के लिए बनाए गए आउटपुट.

कार्रवाइयां

किसी कार्रवाई से, इनपुट के सेट से आउटपुट का सेट जनरेट करने का तरीका पता चलता है. उदाहरण के लिए, "hello.c पर gcc चलाएं और hello.o पाएं". कोई कार्रवाई बनाने पर, Bazel कमांड को तुरंत नहीं चलाता. यह इसे डिपेंडेंसी के ग्राफ़ में रजिस्टर करता है, क्योंकि कोई कार्रवाई किसी दूसरी कार्रवाई के आउटपुट पर निर्भर हो सकती है. उदाहरण के लिए, C में, कंपाइलर के बाद लिंकर को कॉल किया जाना चाहिए.

कार्रवाइयां बनाने वाले सामान्य फ़ंक्शन, ctx.actions में तय किए गए हैं:

  • ctx.actions.run, किसी एक्ज़ीक्यूटेबल को चलाने के लिए.
  • ctx.actions.run_shell, शेल कमांड चलाने के लिए.
  • ctx.actions.write, किसी फ़ाइल में स्ट्रिंग लिखने के लिए.
  • टेंप्लेट से फ़ाइल जनरेट करने के लिए, ctx.actions.expand_template.

ctx.actions.args का इस्तेमाल, कार्रवाइयों के लिए आर्ग्युमेंट को बेहतर तरीके से इकट्ठा करने के लिए किया जा सकता है. यह प्रोसेस, डेटा को तब तक फ़्लैट नहीं करती, जब तक कि उसे लागू नहीं किया जाता:

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 में इकट्ठा करके किया जाता है.

टारगेट के लिए सेवा देने वाली कंपनियों की जानकारी, लागू करने वाले फ़ंक्शन से मिले सेवा देने वाली कंपनियों के ऑब्जेक्ट की सूची से मिलती है.

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

डिफ़ॉल्ट आउटपुट

टारगेट के डिफ़ॉल्ट आउटपुट वे आउटपुट होते हैं जिनका अनुरोध डिफ़ॉल्ट रूप से तब किया जाता है, जब कमांड लाइन पर टारगेट के लिए बिल्ड का अनुरोध किया जाता है. उदाहरण के लिए, किसी java_library टारगेट //pkg:foo के लिए, डिफ़ॉल्ट आउटपुट के तौर पर foo.jar होता है. इसलिए, bazel build //pkg:foo कमांड से वह बन जाएगा.

डिफ़ॉल्ट आउटपुट, DefaultInfo के files पैरामीटर से तय किए जाते हैं:

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

अगर नियम लागू करने पर DefaultInfo नहीं मिलता है या files पैरामीटर नहीं दिया गया है, तो DefaultInfo.files डिफ़ॉल्ट रूप से सभी पहले से तय किए गए आउटपुट पर सेट हो जाता है. आम तौर पर, ये आउटपुट एट्रिब्यूट से बनाए जाते हैं.

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

रनफ़ाइलें

रनफ़ाइलें, फ़ाइलों का एक सेट होता है. इसका इस्तेमाल टारगेट, रनटाइम के दौरान करता है, न कि बिल्डटाइम के दौरान. कार्रवाई के चरण के दौरान, Bazel एक डायरेक्ट्री ट्री बनाता है. इसमें, runfile पर ले जाने वाले सिंबल लिंक होते हैं. इससे बाइनरी के लिए एनवायरमेंट तैयार होता है, ताकि वह रनटाइम के दौरान रनफ़ाइलों को ऐक्सेस कर सके.

नियम बनाते समय, रनफ़ाइलों को मैन्युअल तरीके से जोड़ा जा सकता है. runfiles ऑब्जेक्ट, नियम के संदर्भ ctx.runfiles पर runfiles तरीके से बनाए जा सकते हैं और DefaultInfo पर runfiles पैरामीटर को पास किए जा सकते हैं. कार्रवाई करने वाले नियमों का, रनफ़ाइलों में अपने-आप जुड़ने वाला आउटपुट.

कुछ नियमों में एट्रिब्यूट की जानकारी दी जाती है. आम तौर पर, इनका नाम data होता है. इनके आउटपुट को टारगेट की रनफ़ाइलों में जोड़ा जाता है. रनफ़ाइलों को data के साथ-साथ उन सभी एट्रिब्यूट से भी मर्ज किया जाना चाहिए जिनसे आखिर में एक्ज़ीक्यूशन के लिए कोड मिल सकता है. आम तौर पर, इनमें srcs (इसमें data से जुड़े filegroup टारगेट हो सकते हैं) और deps शामिल हैं.

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

कस्टम प्रोवाइडर

नियम से जुड़ी जानकारी देने के लिए, provider फ़ंक्शन का इस्तेमाल करके, सेवा देने वाली कंपनियों की जानकारी दी जा सकती है:

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields = {
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    },
)

इसके बाद, नियम लागू करने वाले फ़ंक्शन, प्रोवाइडर के इंस्टेंस बना सकते हैं और उन्हें दिखा सकते हैं:

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
सेवा देने वाली कंपनियों को पसंद के मुताबिक शुरू करना

कस्टम प्रीप्रोसेसिंग और पुष्टि करने के लॉजिक की मदद से, किसी प्रोवाइडर के इंस्टैंशिएशन को सुरक्षित किया जा सकता है. इसका इस्तेमाल यह पक्का करने के लिए किया जा सकता है कि सभी प्रोवाइडर इंस्टेंस, कुछ इनवैरिएंट के मुताबिक हों या उपयोगकर्ताओं को इंस्टेंस पाने के लिए बेहतर एपीआई दिया जा सके.

ऐसा करने के लिए, provider फ़ंक्शन में init कॉलबैक पास करें. अगर यह कॉलबैक दिया जाता है, तो provider() का रिटर्न टाइप, दो वैल्यू के ट्यूपल में बदल जाता है: प्रोवाइडर सिंबल, जो init का इस्तेमाल न करने पर सामान्य रिटर्न वैल्यू होता है और "रॉ कंस्ट्रक्टर".

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

इसके उलट, रॉ कंस्ट्रक्टर, init कॉलबैक को बायपास करेगा.

यहां दिए गए उदाहरण में, init का इस्तेमाल करके, अपने आर्ग्युमेंट को प्रीप्रोसेस किया गया है और उनकी पुष्टि की गई है:

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# 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(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

इसके बाद, नियम लागू करने के लिए सेवा देने वाली कंपनी को इस तरह से इंस्टैंशिएट किया जा सकता है:

ExampleInfo(
    files_to_link = my_files_to_link,  # may not be empty
    headers = my_headers,  # will automatically include the core headers
)

रॉ कंस्ट्रक्टर का इस्तेमाल, ऐसे अन्य सार्वजनिक फ़ैक्ट्री फ़ंक्शन तय करने के लिए किया जा सकता है जो init लॉजिक का इस्तेमाल नहीं करते. उदाहरण के लिए, exampleinfo.bzl में ये चीज़ें तय की जा सकती हैं:

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

आम तौर पर, रॉ कन्स्ट्रक्टर को ऐसे वैरिएबल से बंधा होता है जिसका नाम अंडरस्कोर (ऊपर दिया गया _new_exampleinfo) से शुरू होता है. ऐसा इसलिए किया जाता है, ताकि उपयोगकर्ता कोड इसे लोड न कर सके और ऐसेट देने वाली कंपनी के मनमुताबिक इंस्टेंस जनरेट न कर सके.

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

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

लागू किए जा सकने वाले नियम और जांच के नियम

लागू किए जा सकने वाले नियम, ऐसे टारगेट तय करते हैं जिन्हें bazel run कमांड से चालू किया जा सकता है. टेस्ट नियम, एक खास तरह के लागू किए जा सकने वाले नियम होते हैं. इनके टारगेट को bazel test निर्देश से भी शुरू किया जा सकता है. rule को कॉल करते समय, executable या test आर्ग्युमेंट को True पर सेट करके, लागू किए जा सकने वाले और जांच के नियम बनाए जाते हैं:

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

जांच के नियमों के नाम के आखिर में _test होना चाहिए. (आम तौर पर, टेस्ट टारगेट के नाम भी _test पर खत्म होते हैं, लेकिन ऐसा करना ज़रूरी नहीं है.) जिन नियमों को टेस्ट नहीं किया जाना है उनमें यह सफ़िक्स नहीं होना चाहिए.

दोनों तरह के नियमों से एक ऐसी आउटपुट फ़ाइल जनरेट होनी चाहिए जिसे चलाया जा सके. यह फ़ाइल पहले से एलान की गई हो या न हो. इस फ़ाइल को run या test निर्देशों से चालू किया जाएगा. Bazel को यह बताने के लिए कि किसी नियम के कौनसे आउटपुट को इस एक्सीक्यूटेबल के तौर पर इस्तेमाल करना है, उसे DefaultInfo प्रोवाइडर के executable आर्ग्युमेंट के तौर पर पास करें. यह executable, नियम के डिफ़ॉल्ट आउटपुट में जोड़ा जाता है. इसलिए, आपको इसे executable और files, दोनों में पास करने की ज़रूरत नहीं है. इसे runfiles में भी अपने-आप जोड़ दिया जाता है:

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

इस फ़ाइल को जनरेट करने वाली कार्रवाई को, फ़ाइल पर एक्ज़ीक्यूटेबल बिट सेट करना होगा. ctx.actions.run या ctx.actions.run_shell कार्रवाई के लिए, यह कार्रवाई उस टूल से की जानी चाहिए जिसे कार्रवाई से ट्रिगर किया जाता है. ctx.actions.write कार्रवाई के लिए, is_executable = True पास करें.

लेगसी व्यवहार के तौर पर, लागू किए जा सकने वाले नियमों का एक खास ctx.outputs.executable पहले से तय आउटपुट होता है. अगर DefaultInfo का इस्तेमाल करके कोई एक्ज़ीक्यूटेबल फ़ाइल नहीं दी जाती है, तो यह फ़ाइल डिफ़ॉल्ट एक्ज़ीक्यूटेबल फ़ाइल के तौर पर काम करती है. इसका इस्तेमाल किसी और फ़ाइल के लिए नहीं किया जाना चाहिए. आउटपुट के लिए इस तरीके का इस्तेमाल नहीं किया जा सकता, क्योंकि विश्लेषण के समय, यह तरीका, रन की जा सकने वाली फ़ाइल के नाम में बदलाव करने की सुविधा नहीं देता.

कार्रवाई करने वाले नियम और जांच करने वाले नियम के उदाहरण देखें.

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

def example_test(size = "small", **kwargs):
  _example_test(size = size, **kwargs)

_example_test = rule(
 ...
)

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

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

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

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

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

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

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

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

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

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

कॉन्फ़िगरेशन फ़्रैगमेंट

नियम, cpp और java जैसे कॉन्फ़िगरेशन फ़्रैगमेंट ऐक्सेस कर सकते हैं. हालांकि, ऐक्सेस से जुड़ी गड़बड़ियों से बचने के लिए, सभी ज़रूरी फ़्रेगमेंट का एलान करना ज़रूरी है:

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
    ...
)

आम तौर पर, runfiles ट्री में किसी फ़ाइल का रिलेटिव पाथ, सोर्स ट्री या जनरेट किए गए आउटपुट ट्री में उस फ़ाइल के रिलेटिव पाथ जैसा ही होता है. अगर किसी वजह से इनके लिए अलग-अलग वैल्यू देनी हैं, तो root_symlinks या symlinks आर्ग्युमेंट तय किए जा सकते हैं. root_symlinks, फ़ाइलों के पाथ को मैप करने वाली डिक्शनरी है. यहां पाथ, runfiles डायरेक्ट्री के रूट से जुड़े होते हैं. 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, कवरेज मोड में True दिखाता है:

# 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 नहीं दिखाया जाता है, तो हर ऐसे डिपेंडेंसी एट्रिब्यूट के साथ एक डिफ़ॉल्ट एट्रिब्यूट बनाया जाता है जो एट्रिब्यूट स्कीमा में cfg को "exec" पर सेट नहीं करता.dependency_attributes (यह सही तरीका नहीं है, क्योंकि यह srcs जैसे एट्रिब्यूट को source_attributes के बजाय dependency_attributes में डालता है. हालांकि, इससे डिपेंडेंसी चेन में सभी नियमों के लिए, कवरेज कॉन्फ़िगरेशन की ज़रूरत नहीं होती.)

पुष्टि करने से जुड़ी कार्रवाइयां

कभी-कभी आपको बिल्ड के बारे में कुछ की पुष्टि करनी पड़ती है. पुष्टि करने के लिए ज़रूरी जानकारी, सिर्फ़ आर्टफ़ैक्ट (सोर्स फ़ाइलें या जनरेट की गई फ़ाइलें) में उपलब्ध होती है. यह जानकारी आर्टफ़ैक्ट में होती है. इसलिए, विश्लेषण के समय नियम, इस पुष्टि को नहीं कर सकते, क्योंकि नियम फ़ाइलें नहीं पढ़ सकते. इसके बजाय, कार्रवाइयों को लागू करने के समय इसकी पुष्टि करनी चाहिए. पुष्टि न होने पर, कार्रवाई पूरी नहीं होगी और इसलिए बिल्ड भी पूरा नहीं होगा.

पुष्टि करने के लिए, स्टैटिक विश्लेषण, लिंटिंग, डिपेंडेंसी और कंसिस्टेंसी की जांच, और स्टाइल की जांच जैसी प्रोसेस इस्तेमाल की जा सकती हैं.

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

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

यह काम करता है, क्योंकि कंपाइल करने की कार्रवाई शुरू होने पर, Bazel हमेशा पुष्टि करने की कार्रवाई करता है. हालांकि, इसकी कुछ अहम कमियां हैं:

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

  2. अगर कंपाइल करने की कार्रवाई के बजाय, बिल्ड में अन्य कार्रवाइयां चल सकती हैं, तो पुष्टि करने वाली कार्रवाइयों के खाली आउटपुट को उन कार्रवाइयों में भी जोड़ना होगा. उदाहरण के लिए, java_library का सोर्स jar आउटपुट. अगर बाद में, संकलन कार्रवाई के बजाय चलने वाली नई कार्रवाइयां जोड़ी जाती हैं और पुष्टि करने के लिए खाली आउटपुट को गलती से छोड़ दिया जाता है, तो भी यह समस्या होती है.

इन समस्याओं को हल करने के लिए, पुष्टि करने वाले आउटपुट ग्रुप का इस्तेमाल करें.

पुष्टि करने के लिए आउटपुट ग्रुप

पुष्टि करने की कार्रवाइयों के आउटपुट ग्रुप को इस तरह से डिज़ाइन किया गया है कि पुष्टि करने की कार्रवाइयों के ऐसे आउटपुट को इसमें रखा जा सके जिनका इस्तेमाल नहीं किया जाता. इससे, उन्हें अन्य कार्रवाइयों के इनपुट में कृत्रिम तरीके से जोड़ने की ज़रूरत नहीं पड़ती.

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

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

टारगेट की पुष्टि करने वाली कार्रवाइयां, इन तीन मामलों में नहीं चलाई जातीं:

  • जब टारगेट को टूल के तौर पर इस्तेमाल किया जाता है
  • जब टारगेट पर, किसी छिपी हुई डिपेंडेंसी के तौर पर निर्भर किया जाता है. उदाहरण के लिए, "_" से शुरू होने वाला एट्रिब्यूट
  • जब टारगेट, एक्सीक्यूट कॉन्फ़िगरेशन में बनाया जाता है.

यह माना जाता है कि इन टारगेट के पास अपने अलग-अलग बिल्ड और टेस्ट होते हैं, जिनसे पुष्टि करने में हुई किसी भी गड़बड़ी का पता चलता है.

पुष्टि करने के लिए आउटपुट ग्रुप का इस्तेमाल करना

पुष्टि करने वाले आउटपुट ग्रुप का नाम _validation है और इसका इस्तेमाल किसी भी दूसरे आउटपुट ग्रुप की तरह किया जाता है:

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")
  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
    outputs = [validation_output],
    executable = ctx.executable._validation_tool,
    arguments = [validation_output.path],
  )

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"
    ),
  }
)

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

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

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

पुष्टि करने की कार्रवाइयों का फ़्लैग

पुष्टि करने वाली कार्रवाइयों को --run_validations कमांड लाइन के फ़्लैग से कंट्रोल किया जाता है. यह डिफ़ॉल्ट रूप से 'सही' पर सेट होता है.

बंद की गई सुविधाएं

पहले से तय किए गए आउटपुट, जो अब काम नहीं करते

पहले से तय किए गए आउटपुट का इस्तेमाल करने के दो पुराने तरीके हैं:

  • rule के outputs पैरामीटर से, पहले से तय किए गए आउटपुट लेबल जनरेट करने के लिए, आउटपुट एट्रिब्यूट के नामों और स्ट्रिंग टेंप्लेट के बीच मैपिंग की जानकारी मिलती है. पहले से एलान न किए गए आउटपुट का इस्तेमाल करें और DefaultInfo.files में आउटपुट को साफ़ तौर पर जोड़ें. पहले से तय किए गए आउटपुट के लेबल के बजाय, आउटपुट का इस्तेमाल करने वाले नियमों के लिए, नियम के टारगेट के लेबल का इस्तेमाल इनपुट के तौर पर करें.

  • कार्रवाई करने वाले नियमों के लिए, ctx.outputs.executable का मतलब, पहले से तय किए गए ऐसे आउटपुट से है जिसका नाम नियम के टारगेट से मेल खाता है. आउटपुट को साफ़ तौर पर बताएं. उदाहरण के लिए, ctx.actions.declare_file(ctx.label.name) के साथ. साथ ही, पक्का करें कि एक्सीक्यूटेबल जनरेट करने वाला निर्देश, उसे चलाने की अनुमति देने के लिए उसकी अनुमतियां सेट करता हो. एक्ज़ीक्यूटेबल आउटपुट को DefaultInfo के executable पैरामीटर में साफ़ तौर पर पास करें.

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 ऑब्जेक्ट पर मौजूद सामान्य फ़ील्ड होते थे. इन्हें बिंदु ऑपरेटर का इस्तेमाल करके ऐक्सेस किया गया था. साथ ही, इन्हें फ़ील्ड को प्रोवाइडर ऑब्जेक्ट की सूची के बजाय, नियम के लागू करने वाले फ़ंक्शन से मिले struct में डालकर बनाया गया था:

return struct(example_info = struct(headers = depset(...)))

इस तरह के सप्लायर, Target ऑब्जेक्ट के संबंधित फ़ील्ड से वापस पाए जा सकते हैं:

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

इस स्टाइल का इस्तेमाल नहीं किया जाना चाहिए. इसे नए कोड में इस्तेमाल नहीं किया जाना चाहिए; माइग्रेट करने में मदद पाने के लिए, यहां दी गई जानकारी देखें. प्रोवाइडर के नए तरीके से, नामों के मेल खाने से बचा जा सकता है. यह डेटा छिपाने की सुविधा भी देता है. इसके लिए, किसी भी कोड को प्रोवाइडर इंस्टेंस को ऐक्सेस करने के लिए, प्रोवाइडर सिंबल का इस्तेमाल करके उसे वापस लाना होगा.

फ़िलहाल, लेगसी सेवा देने वाली कंपनियों के साथ भी काम किया जा सकता है. कोई नियम, लेगसी और आधुनिक, दोनों तरह के प्रोवाइडर को इस तरह दिखा सकता है:

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. सभी नियमों से लेगसी प्रोवाइडर को पूरी तरह हटाएं.