नियम

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

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

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

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

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

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

Bazel, 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 मैक्रो कहा जाता है. स्टारलार्क मैक्रो को 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]

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

Files

फ़ाइलों को 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.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 एक डायरेक्ट्री ट्री बनाता है. इसमें रनफ़ाइल पर ले जाने वाले सिमलिंक होते हैं. इससे बाइनरी के लिए एनवायरमेंट तय होता है, ताकि यह रनटाइम के दौरान रनफ़ाइल को ऐक्सेस कर सके.

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

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

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

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

लागू किए जा सकने वाले नियम और जांच के नियम के उदाहरण देखें.

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

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

_example_test = rule(
 ...
)

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

जब किसी एक्ज़ीक्यूटेबल टारगेट को bazel run (या test) के साथ चलाया जाता है, तो रनफ़ाइल डायरेक्ट्री का रूट, एक्ज़ीक्यूटेबल के पास होता है. पाथ इस तरह से जुड़े होते हैं:

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

रनफ़ाइल डायरेक्ट्री में, File का पाथ File.short_path से जुड़ा होता है.

bazel से सीधे तौर पर एक्ज़ीक्यूट की गई बाइनरी, runfiles डायरेक्ट्री के रूट के पास है. हालांकि, रनफ़ाइल से from नाम वाली बाइनरी भी, ऐसा अनुमान नहीं लगा सकती. इसे कम करने के लिए, हर बाइनरी को एनवायरमेंट या कमांड लाइन तर्क या फ़्लैग का इस्तेमाल करके, पैरामीटर के रूप में अपनी रनफ़ाइल रूट को स्वीकार करने का तरीका उपलब्ध कराना चाहिए. इससे बाइनरी फ़ाइल, सही कैननिकल रनफ़ाइल रूट को उन बाइनरी में पास कर पाती हैं जिन्हें वह कॉल करता है. अगर इसे सेट नहीं किया जाता है, तो बाइनरी अनुमान लगा सकती है कि यह पहली बाइनरी कॉल थी और वह पास की रनफ़ाइल डायरेक्ट्री ढूंढती है.

उन्नत विषय

आउटपुट फ़ाइलों का अनुरोध किया जा रहा है

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

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

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

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

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

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

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

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

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

पुष्टि के लिए कार्रवाइयां

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

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

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

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

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

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

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

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

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

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

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

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

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

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

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

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

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

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

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

रनफ़ाइल से जुड़ी सुविधाओं से बचें

ctx.runfiles और runfiles टाइप में कई सुविधाएं हैं. इनमें से कई सुविधाओं को, लेगसी वजहों से सेव करके रखा जाता है. नीचे दिए गए सुझावों की मदद से, काम की जटिलता को कम किया जा सकता है:

  • ctx.runfiles के collect_data और collect_default मोड का इस्तेमाल करने से बचें. ये मोड, गलत तरीके से कुछ हार्डकोड डिपेंडेंसी किनारों पर रनफ़ाइल इकट्ठा करते हैं. इसके बजाय, ctx.runfiles के files या transitive_files पैरामीटर का इस्तेमाल करके या runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) के साथ डिपेंडेंसी से रन फ़ाइलों को मर्ज करके फ़ाइलें जोड़ें.

  • DefaultInfo कंस्ट्रक्टर के data_runfiles और default_runfiles का इस्तेमाल करने से बचें. इसके बजाय, DefaultInfo(runfiles = ...) बताएं. लेगसी वजहों से, "डिफ़ॉल्ट" और "डेटा" रनफ़ाइल के बीच के अंतर को बनाए रखा जाता है. उदाहरण के लिए, कुछ नियम अपने डिफ़ॉल्ट आउटपुट data_runfiles में रखते हैं, लेकिन default_runfiles में नहीं. data_runfiles का इस्तेमाल करने के बजाय, नियमों में डिफ़ॉल्ट आउटपुट शामिल दोनों होने चाहिए. साथ ही, इन्हें उन एट्रिब्यूट से default_runfiles में मर्ज किया जाना चाहिए जो रनफ़ाइल उपलब्ध कराते हैं (अक्सर data).

  • DefaultInfo से runfiles पाने के लिए (आम तौर पर सिर्फ़ मौजूदा नियम और उसकी डिपेंडेंसी के बीच रनफ़ाइल को मर्ज करने के लिए), DefaultInfo.default_runfiles का इस्तेमाल करें, न कि DefaultInfo.data_runfiles का.

लेगसी कंपनियों से माइग्रेट करना

अब तक, Bazel प्रोवाइडर Target ऑब्जेक्ट पर आसान फ़ील्ड हुआ करते थे. इन्हें डॉट ऑपरेटर का इस्तेमाल करके ऐक्सेस किया गया था. इन्हें बनाने के लिए, फ़ील्ड को नियम लागू करने वाले फ़ंक्शन की मदद से दिखाए गए 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. लेगसी सेवा देने वाली कंपनी को सभी नियमों से पूरी तरह हटाएं.