Bzlmod की मदद से एक्सटर्नल डिपेंडेंसी मैनेज करें

Bzlmod , Bazel 5.0 में जोड़े गए, बाहरी डिपेंडेंसी के नए सिस्टम का कोडनेम है. इसे पुराने सिस्टम की कई समस्याओं को हल करने के लिए जोड़ा गया था. इन समस्याओं को धीरे-धीरे ठीक नहीं किया जा सकता था. ज़्यादा जानकारी के लिए, ओरिजनल डिज़ाइन वाले दस्तावेज़ का 'समस्या की जानकारी' सेक्शन देखें .

Bazel 5.0 में, Bzlmod डिफ़ॉल्ट रूप से चालू नहीं होता. इसे चालू करने के लिए, --experimental_enable_bzlmod फ़्लैग की जानकारी देनी होगी. फ़्लैग के नाम से पता चलता है कि यह सुविधा फ़िलहाल एक्सपेरिमेंटल है; आधिकारिक तौर पर लॉन्च होने तक, एपीआई और व्यवहार में बदलाव हो सकते हैं.

अपने प्रोजेक्ट को Bzlmod पर माइग्रेट करने के लिए, Bzlmod माइग्रेशन गाइड देखें. Bzlmod के इस्तेमाल के उदाहरण, उदाहरणों के डेटाबेस में भी देखे जा सकते हैं.

Bazel मॉड्यूल

पुराना WORKSPACE-आधारित बाहरी डिपेंडेंसी सिस्टम, डेटाबेस (या रेपो) पर आधारित है. इन्हें डेटाबेस के नियमों (या रेपो के नियमों) की मदद से बनाया जाता है. नए सिस्टम में, रेपो अब भी एक अहम कॉन्सेप्ट है. हालांकि, मॉड्यूल , डिपेंडेंसी की मुख्य इकाइयां हैं.

मॉड्यूल , असल में एक Bazel प्रोजेक्ट है. इसके कई वर्शन हो सकते हैं. इनमें से हर वर्शन, उन अन्य मॉड्यूल के बारे में मेटाडेटा पब्लिश करता है जिन पर वह निर्भर करता है. यह डिपेंडेंसी मैनेजमेंट के अन्य सिस्टम में मौजूद, जाने-पहचाने कॉन्सेप्ट के जैसा है. जैसे, Maven आर्टफ़ैक्ट, npm पैकेज, Cargo क्रेट, Go मॉड्यूल वगैरह.

कोई मॉड्यूल, WORKSPACE में खास यूआरएल के बजाय, name और version के पेयर का इस्तेमाल करके अपनी डिपेंडेंसी तय करता है. इसके बाद, डिपेंडेंसी को Bazel रजिस्ट्री में ढूंढा जाता है. डिफ़ॉल्ट रूप से, Bazel सेंट्रल रजिस्ट्री में. आपके वर्कस्पेस में, हर मॉड्यूल को रेपो में बदल दिया जाता है.

MODULE.bazel

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

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

MODULE.bazel फ़ाइल, वर्कस्पेस डायरेक्ट्री के रूट में होनी चाहिए. यह WORKSPACE फ़ाइल के बगल में होती है. WORKSPACE फ़ाइल के उलट, आपको ट्रांज़िटिव डिपेंडेंसी तय करने की ज़रूरत नहीं होती. इसके बजाय, आपको सिर्फ़ सीधी डिपेंडेंसी तय करनी चाहिए. साथ ही, ट्रांज़िटिव डिपेंडेंसी को अपने-आप ढूंढने के लिए, आपकी डिपेंडेंसी की MODULE.bazel फ़ाइलों को प्रोसेस किया जाता है.

MODULE.bazel फ़ाइल, BUILD फ़ाइलों की तरह होती है, क्योंकि इसमें कंट्रोल फ़्लो के किसी भी फ़ॉर्म का इस्तेमाल नहीं किया जा सकता. इसके अलावा, इसमें load स्टेटमेंट का इस्तेमाल भी नहीं किया जा सकता. MODULE.bazel फ़ाइलों में इन डायरेक्टिव का इस्तेमाल किया जा सकता है:

  • module, मौजूदा मॉड्यूल के बारे में मेटाडेटा तय करने के लिए. इसमें उसका नाम, वर्शन वगैरह शामिल है;
  • bazel_dep, Bazel के अन्य मॉड्यूल पर सीधी डिपेंडेंसी तय करने के लिए;
  • Overrides. इनका इस्तेमाल सिर्फ़ रूट मॉड्यूल कर सकता है. यानी, ऐसा मॉड्यूल नहीं जिसे डिपेंडेंसी के तौर पर इस्तेमाल किया जा रहा है. इनकी मदद से, किसी सीधी या ट्रांज़िटिव डिपेंडेंसी के व्यवहार को पसंद के मुताबिक बनाया जा सकता है:
  • मॉड्यूल एक्सटेंशन से जुड़े डायरेक्टिव: मॉड्यूल एक्सटेंशन:

वर्शन का फ़ॉर्मैट

Bazel का एक बड़ा ईकोसिस्टम है. प्रोजेक्ट में वर्शनिंग की अलग-अलग स्कीम का इस्तेमाल किया जाता है. सबसे ज़्यादा इस्तेमाल की जाने वाली स्कीम SemVer है. हालांकि, Abseil जैसे अहम प्रोजेक्ट में भी अलग-अलग स्कीम का इस्तेमाल किया जाता है. इसके वर्शन, तारीख पर आधारित होते हैं. उदाहरण के लिए, 20210324.2).

इस वजह से, Bzlmod, SemVer की खास जानकारी के ज़्यादा आसान वर्शन का इस्तेमाल करता है. इनमें ये अंतर शामिल हैं:

  • SemVer के मुताबिक, वर्शन के "रिलीज़" वाले हिस्से में तीन सेगमेंट होने चाहिए: MAJOR.MINOR.PATCH. Bazel में, इस शर्त को आसान बनाया गया है, ताकि सेगमेंट की कोई भी संख्या इस्तेमाल की जा सके.
  • SemVer में, "रिलीज़" वाले हिस्से के हर सेगमेंट में सिर्फ़ अंक होने चाहिए. Bazel में, इसे आसान बनाया गया है, ताकि अक्षरों का इस्तेमाल भी किया जा सके. साथ ही, तुलना के सिमैंटिक, "प्रिरिलीज़" वाले हिस्से में मौजूद "आइडेंटिफ़ायर" से मेल खाते हैं.
  • इसके अलावा, मेजर, माइनर, और पैच वर्शन में बढ़ोतरी के सिमैंटिक लागू नहीं किए जाते. (हालांकि, पुराने सिस्टम के साथ काम करने की सुविधा के बारे में जानने के लिए, कंपैटबिलिटी लेवल देखें.)

SemVer का कोई भी मान्य वर्शन, Bazel मॉड्यूल का मान्य वर्शन होता है. इसके अलावा, SemVer के दो वर्शन a और b की तुलना करने पर, a < b तब होता है, जब Bazel मॉड्यूल के वर्शन के तौर पर तुलना करने पर भी यही नतीजा मिलता है.

वर्शन रिज़ॉल्यूशन

डायमंड डिपेंडेंसी की समस्या, वर्शन वाली डिपेंडेंसी मैनेजमेंट में आम है. मान लें कि आपके पास डिपेंडेंसी का यह ग्राफ़ है:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

डी के किस वर्शन का इस्तेमाल किया जाना चाहिए? इस सवाल का जवाब देने के लिए, Bzlmod, Go मॉड्यूल सिस्टम में जोड़े गए, मिनिमल वर्शन सिलेक्शन (एमवीएस) एल्गोरिदम का इस्तेमाल करता है. एमवीएस का मानना है कि किसी मॉड्यूल के सभी नए वर्शन, पुराने सिस्टम के साथ काम करते हैं. इसलिए, यह सीधे तौर पर किसी भी डिपेंडेंट (हमारे उदाहरण में डी 1.1) के तय किए गए सबसे नए वर्शन को चुनता है. इसे "मिनिमल" इसलिए कहा जाता है, क्योंकि यहां डी 1.1, मिनिमल वर्शन है जो हमारी ज़रूरतों को पूरा कर सकता है. भले ही, डी 1.2 या नया वर्शन मौजूद हो, हम उन्हें नहीं चुनते. इसका एक और फ़ायदा यह है कि वर्शन का चुनाव हाई-फ़िडेलिटी और दोहराया जा सकने वाला होता है.

वर्शन रिज़ॉल्यूशन, रजिस्ट्री के बजाय आपकी मशीन पर स्थानीय तौर पर किया जाता है.

कंपैटबिलिटी लेवल

ध्यान दें कि पुराने सिस्टम के साथ काम करने की सुविधा के बारे में एमवीएस का अनुमान सही है, क्योंकि यह किसी मॉड्यूल के पुराने सिस्टम के साथ काम न करने वाले वर्शन को, अलग मॉड्यूल के तौर पर मानता है. SemVer के हिसाब से, इसका मतलब है कि A 1.x और A 2.x को अलग-अलग मॉड्यूल माना जाता है. साथ ही, इन्हें रिज़ॉल्व किए गए डिपेंडेंसी ग्राफ़ में एक साथ इस्तेमाल किया जा सकता है. ऐसा इसलिए हो पाता है, क्योंकि Go में पैकेज पाथ में मेजर वर्शन को एनकोड किया जाता है. इसलिए, कंपाइल-टाइम या लिंकिंग-टाइम में कोई टकराव नहीं होता.

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

डेटाबेस के नाम

Bazel में, हर बाहरी डिपेंडेंसी का एक डेटाबेस का नाम होता है. कभी-कभी, एक ही डिपेंडेंसी को अलग-अलग डेटाबेस के नामों से इस्तेमाल किया जा सकता है. उदाहरण के लिए, दोनों @io_bazel_skylib और @bazel_skylib का मतलब है Bazel skylib. इसके अलावा, अलग-अलग प्रोजेक्ट में, अलग-अलग डिपेंडेंसी के लिए एक ही डेटाबेस का नाम इस्तेमाल किया जा सकता है.

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

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

    • Bazel मॉड्यूल रेपो के लिए: module_name~version
      (उदाहरण. @bazel_skylib~1.0.3)
    • मॉड्यूल एक्सटेंशन रेपो के लिए: module_name~version~extension_name~repo_name
      (उदाहरण. @rules_cc~0.0.1~cc_configure~local_config_cc)
  • डेटाबेस का असली नाम: किसी रेपो में मौजूद BUILD और .bzl फ़ाइलों में इस्तेमाल किया जाने वाला डेटाबेस का नाम. एक ही डिपेंडेंसी के अलग-अलग रेपो में, अलग-अलग असली नाम हो सकते हैं.
    इसे इस तरह तय किया जाता है:

    • Bazel मॉड्यूल रेपो के लिए: module_name डिफ़ॉल्ट रूप से या repo_name एट्रिब्यूट से तय किया गया नाम जो bazel_dep में मौजूद है.
    • मॉड्यूल एक्सटेंशन रेपो के लिए: use_repoके ज़रिए जोड़ा गया डेटाबेस का नाम.

हर डेटाबेस में, उसकी सीधी डिपेंडेंसी का डेटाबेस मैपिंग डिक्शनरी होती है. यह असली डेटाबेस के नाम से कैननिकल डेटाबेस के नाम तक का मैप होता है. लेबल बनाते समय, हम डेटाबेस के नाम को रिज़ॉल्व करने के लिए, डेटाबेस मैपिंग का इस्तेमाल करते हैं. ध्यान दें कि कैननिकल डेटाबेस के नामों में कोई टकराव नहीं होता. साथ ही, MODULE.bazel फ़ाइल को पार्स करके, असली डेटाबेस के नामों के इस्तेमाल का पता लगाया जा सकता है. इसलिए, टकरावों को आसानी से पकड़ा और हल किया जा सकता है. इससे अन्य डिपेंडेंसी पर कोई असर नहीं पड़ता.

स्ट्रिक्ट डिप्स

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

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

  • Bazel मॉड्यूल रेपो, MODULE.bazel फ़ाइल में bazel_dep और use_repo के ज़रिए जोड़े गए सभी रेपो को देख सकता है.
  • मॉड्यूल एक्सटेंशन रेपो, उस मॉड्यूल की सभी दिखने वाली डिपेंडेंसी को देख सकता है जो एक्सटेंशन उपलब्ध कराता है. इसके अलावा, वह उसी मॉड्यूल एक्सटेंशन से जनरेट किए गए अन्य सभी रेपो को भी देख सकता है.

रजिस्ट्री

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

इंडेक्स रजिस्ट्री

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

इंडेक्स रजिस्ट्री का फ़ॉर्मैट यह होना चाहिए:

  • /bazel_registry.json: यह एक JSON फ़ाइल है. इसमें रजिस्ट्री के लिए मेटाडेटा होता है. जैसे:
    • mirrors, सोर्स संग्रहों के लिए इस्तेमाल किए जाने वाले मिरर की सूची तय करना.
    • module_base_path, source.json फ़ाइल में local_repository टाइप वाले मॉड्यूल के लिए, बेस पाथ तय करना.
  • /modules: यह एक डायरेक्ट्री है. इसमें इस रजिस्ट्री में मौजूद हर मॉड्यूल के लिए एक सबडायरेक्ट्री होती है.
  • /modules/$MODULE: यह एक डायरेक्ट्री है. इसमें इस मॉड्यूल के हर वर्शन के लिए एक सबडायरेक्ट्री होती है. साथ ही, इसमें यह फ़ाइल भी होती है:
    • metadata.json: यह एक JSON फ़ाइल है. इसमें मॉड्यूल के बारे में जानकारी होती है. इसमें ये फ़ील्ड शामिल होते हैं:
      • homepage: प्रोजेक्ट के होम पेज का यूआरएल.
      • maintainers: JSON ऑब्जेक्ट की सूची. इनमें से हर ऑब्जेक्ट, रजिस्ट्री में मॉड्यूल के रखरखाव करने वाले व्यक्ति की जानकारी से जुड़ा होता है. ध्यान दें कि यह ज़रूरी नहीं है कि यह प्रोजेक्ट के लेखकों के जैसा हो.
      • versions: इस रजिस्ट्री में मौजूद इस मॉड्यूल के सभी वर्शन की सूची.
      • yanked_versions: इस मॉड्यूल के yanked वर्शन की सूची. फ़िलहाल, यह कोई कार्रवाई नहीं करता. हालांकि, आने वाले समय में, yanked वर्शन को छोड़ दिया जाएगा या गड़बड़ी दिखाई जाएगी.
  • /modules/$MODULE/$VERSION: यह एक डायरेक्ट्री है. इसमें ये फ़ाइलें शामिल होती हैं:
    • MODULE.bazel: इस मॉड्यूल वर्शन की MODULE.bazel फ़ाइल.
    • source.json: यह एक JSON फ़ाइल है. इसमें इस मॉड्यूल वर्शन का सोर्स फ़ेच करने के तरीके के बारे में जानकारी होती है.
      • डिफ़ॉल्ट टाइप "archive" है. इसमें ये फ़ील्ड शामिल हैं:
        • url: सोर्स संग्रह का यूआरएल.
        • integrity: संग्रह का सबरिसॉर्स इंटिग्रिटी चेकसम.
        • strip_prefix: सोर्स संग्रह को एक्सट्रैक्ट करते समय हटाने के लिए, डायरेक्ट्री प्रीफ़िक्स.
        • patches: स्ट्रिंग की सूची. इनमें से हर स्ट्रिंग, एक्सट्रैक्ट किए गए संग्रह पर लागू करने के लिए, पैच फ़ाइल का नाम बताती है. पैच फ़ाइलें, /modules/$MODULE/$VERSION/patches डायरेक्ट्री में मौजूद होती हैं.
        • patch_strip: Unix पैच के --strip आर्ग्युमेंट के जैसा.
      • टाइप को बदलकर, इन फ़ील्ड के साथ स्थानीय पाथ का इस्तेमाल किया जा सकता है:
        • type: local_path
        • path: रेपो का स्थानीय पाथ. इसकी गिनती इस तरह की जाती है:
          • अगर पाथ, ऐब्सलूट पाथ है, तो इसका इस्तेमाल वैसे ही किया जाएगा.
          • अगर पाथ, रिलेटिव पाथ है और module_base_path ऐब्सलूट पाथ है, तो पाथ को <module_base_path>/<path> के तौर पर रिज़ॉल्व किया जाता है.
          • अगर पाथ और module_base_path दोनों रिलेटिव पाथ हैं, तो पाथ को <registry_path>/<module_base_path>/<path> के तौर पर रिज़ॉल्व किया जाता है. रजिस्ट्री को स्थानीय तौर पर होस्ट किया जाना चाहिए और --registry=file://<registry_path> का इस्तेमाल करके इसे ऐक्सेस किया जाना चाहिए. ऐसा न करने पर, Bazel गड़बड़ी दिखाएगा.
    • patches/: यह एक ज़रूरी नहीं है. इसमें पैच फ़ाइलें होती हैं. इसका इस्तेमाल सिर्फ़ तब किया जाता है, जब source.json का टाइप "archive" होता है.

Bazel सेंट्रल रजिस्ट्री

Bazel सेंट्रल रजिस्ट्री (बीसीआर), bcr.bazel.build पर मौजूद एक इंडेक्स रजिस्ट्री है. इसका कॉन्टेंट GitHub रेपो bazelbuild/bazel-central-registryसे लिया जाता है.

बीसीआर को Bazel कम्यूनिटी मैनेज करती है. इसमें योगदान देने वाले लोग, पुल अनुरोध सबमिट कर सकते हैं. Bazel सेंट्रल रजिस्ट्री की नीतियां और प्रोसेस देखें .

बीसीआर के लिए, सामान्य इंडेक्स रजिस्ट्री के फ़ॉर्मैट के अलावा, हर मॉड्यूल वर्शन (/modules/$MODULE/$VERSION/presubmit.yml) के लिए presubmit.yml फ़ाइल की ज़रूरत होती है. इस फ़ाइल में, कुछ ज़रूरी बिल्ड और टेस्ट टारगेट तय किए जाते हैं. इनका इस्तेमाल, इस मॉड्यूल वर्शन की वैधता की जांच करने के लिए किया जा सकता है. साथ ही, बीसीआर के सीआई पाइपलाइन इनका इस्तेमाल, बीसीआर में मौजूद मॉड्यूल के बीच इंटरऑपरेबिलिटी पक्का करने के लिए करती हैं.

रजिस्ट्री चुनना

Bazel के दोहराए जा सकने वाले फ़्लैग --registry का इस्तेमाल, उन रजिस्ट्री की सूची तय करने के लिए किया जा सकता है जिनसे मॉड्यूल का अनुरोध करना है. इसलिए, अपने प्रोजेक्ट को तीसरे पक्ष या इंटरनल रजिस्ट्री से डिपेंडेंसी फ़ेच करने के लिए सेट अप किया जा सकता है. पहले की रजिस्ट्री को प्राथमिकता दी जाती है. सुविधा के लिए, अपने प्रोजेक्ट की .bazelrc फ़ाइल में --registry फ़्लैग की सूची डाली जा सकती है.

मॉड्यूल एक्सटेंशन

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

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

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

ऊपर दिए गए डिपेंडेंसी ग्राफ़ में, A 1.1 और B 1.2 वगैरह, Bazel मॉड्यूल हैं. इनमें से हर एक को MODULE.bazel फ़ाइल के तौर पर माना जा सकता है. हर मॉड्यूल, मॉड्यूल एक्सटेंशन के लिए कुछ टैग तय कर सकता है. यहां, "maven" एक्सटेंशन के लिए कुछ टैग और "cargo" के लिए कुछ टैग तय किए गए हैं. जब यह डिपेंडेंसी ग्राफ़ पूरा हो जाता है. उदाहरण के लिए, हो सकता है कि B 1.2 में असल में D 1.3 पर bazel_dep हो, लेकिन C की वजह से इसे D 1.4 पर अपग्रेड कर दिया गया हो. इसके बाद, "maven" एक्सटेंशन चलाया जाता है. इसे maven.* के सभी टैग पढ़ने की अनुमति मिलती है. साथ ही, यह तय करने के लिए कि किन रेपो को बनाना है, इसमें मौजूद जानकारी का इस्तेमाल किया जाता है. इसी तरह, "cargo" एक्सटेंशन के लिए भी.

एक्सटेंशन का इस्तेमाल

एक्सटेंशन, Bazel मॉड्यूल में ही होस्ट किए जाते हैं. इसलिए, अपने मॉड्यूल में किसी एक्सटेंशन का इस्तेमाल करने के लिए, आपको पहले उस मॉड्यूल पर bazel_dep जोड़ना होगा. इसके बाद, इसे स्कोप में लाने के लिए, use_extension इन-बिल्ट फ़ंक्शन को कॉल करना होगा. यहां एक उदाहरण दिया गया है. यह rules_jvm_external मॉड्यूल में तय किए गए, काल्पनिक "maven" एक्सटेंशन का इस्तेमाल करने के लिए, MODULE.bazel फ़ाइल का एक स्निपेट है:

bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

एक्सटेंशन को स्कोप में लाने के बाद, इसके लिए टैग तय करने के लिए, डॉट-सिंटैक्स का इस्तेमाल किया जा सकता है. ध्यान दें कि टैग को, उनसे जुड़ी टैग क्लास से तय किए गए स्कीमा के मुताबिक होना चाहिए. इसके बारे में नीचे एक्सटेंशन की परिभाषा में बताया गया है. यहां maven.dep और maven.pom के कुछ टैग तय करने का एक उदाहरण दिया गया है.

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

अगर एक्सटेंशन ऐसे रेपो जनरेट करता है जिनका इस्तेमाल आपको अपने मॉड्यूल में करना है, तो उन्हें तय करने के लिए, use_repo डायरेक्टिव का इस्तेमाल करें. ऐसा, स्ट्रिक्ट डिप्स की शर्त को पूरा करने और स्थानीय रेपो के नाम के टकराव से बचने के लिए किया जाता है.

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

किसी एक्सटेंशन से जनरेट किए गए रेपो, उसके एपीआई का हिस्सा होते हैं. इसलिए, आपके तय किए गए टैग से, आपको पता होना चाहिए कि "maven" एक्सटेंशन, "org_junit_junit" नाम का एक रेपो और "com_google_guava_guava" नाम का एक रेपो जनरेट करने वाला है. use_repo की मदद से, आपके पास अपने मॉड्यूल के स्कोप में, इनके नाम बदलने का विकल्प होता है. जैसे, यहां "guava".

एक्सटेंशन की परिभाषा

मॉड्यूल एक्सटेंशन को, रेपो के नियमों की तरह ही तय किया जाता है. इसके लिए, module_extension फ़ंक्शन का इस्तेमाल किया जाता है. दोनों में एक इंप्लीमेंटेशन फ़ंक्शन होता है. हालांकि, रेपो के नियमों में कई एट्रिब्यूट होते हैं, जबकि मॉड्यूल एक्सटेंशन में कई tag_classes होते हैं. इनमें से हर एक में कई एट्रिब्यूट होते हैं. टैग क्लास, इस एक्सटेंशन के इस्तेमाल किए जाने वाले टैग के लिए स्कीमा तय करती हैं. ऊपर दिए गए, काल्पनिक "maven" एक्सटेंशन के उदाहरण को जारी रखते हुए:

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

इन तय की गई चीज़ों से यह साफ़ हो जाता है कि maven.dep और maven.pom टैग तय किए जा सकते हैं. इसके लिए, ऊपर तय किए गए एट्रिब्यूट स्कीमा का इस्तेमाल किया जाता है.

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

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

ऊपर दिए गए उदाहरण में, हम डिपेंडेंसी ग्राफ़ (ctx.modules) में मौजूद सभी मॉड्यूल को देखते हैं. इनमें से हर एक, bazel_module ऑब्जेक्ट है. इसका tags फ़ील्ड maven.* टैग दिखाता है. इसके बाद, हम सीएलआई यूटिलिटी Coursier को लागू करके, Maven से संपर्क करते हैं और रिज़ॉल्यूशन करते हैं. आखिर में, हम रिज़ॉल्यूशन के नतीजे का इस्तेमाल करके, कई रेपो बनाते हैं. इसके लिए, काल्पनिक maven_single_jar रेपो के नियम का इस्तेमाल करते हैं.