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

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

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

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

Bazel मॉड्यूल

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

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

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

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 फ़ाइलों के साथ इन निर्देशों का पालन किया जा सकता है:

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

Bazel के पास कई तरह का नेटवर्क है. साथ ही, उसके प्रोजेक्ट में अलग-अलग वर्शनिंग स्कीम इस्तेमाल की जाती है. SemVer, अब तक का सबसे लोकप्रिय प्रोजेक्ट है. हालांकि, कुछ ऐसे प्रोजेक्ट भी हैं जिनमें कई अलग-अलग स्कीम का इस्तेमाल किया गया है. जैसे, Abseil, जिनके वर्शन तारीख के हिसाब से होते हैं, जैसे कि 20210324.2).

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

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

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

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

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

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

D का कौनसा वर्शन इस्तेमाल करना चाहिए? इस सवाल को हल करने के लिए, Bzlmod, Go मॉड्यूल सिस्टम में पेश किए गए कम से कम वर्शन चुनने (एमवीएस) एल्गोरिदम का इस्तेमाल करता है. MVS यह मानता है कि किसी मॉड्यूल के सभी नए वर्शन, पुराने सिस्टम के साथ काम करते हैं. इसलिए, यह किसी भी डिपेंडेंट के बताए गए सबसे ऊंचे वर्शन (हमारे उदाहरण में D 1.1) को चुनता है. इसे "मिनिमल" कहा जाता है, क्योंकि यहां D 1.1 मिनिमल वर्शन है जो हमारी ज़रूरी शर्तों को पूरा कर सकता है. अगर D 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 या bazel_dep में repo_name एट्रिब्यूट से दिया गया नाम.
    • मॉड्यूल एक्सटेंशन रिपॉज़िटरी के लिए: 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: इस मॉड्यूल के यंक किए गए वर्शन की सूची. फ़िलहाल, यह उपलब्ध नहीं है, लेकिन आने वाले समय में, यंक किए गए वर्शन को स्किप कर दिया जाएगा या गड़बड़ी वाला एक मैसेज मिलेगा.
  • /modules/$MODULE/$VERSION: इस डायरेक्ट्री में ये फ़ाइलें शामिल हैं:
    • MODULE.bazel: इस मॉड्यूल वर्शन की MODULE.bazel फ़ाइल.
    • source.json: एक JSON फ़ाइल, जिसमें इस मॉड्यूल वर्शन के सोर्स को फ़ेच करने के तरीके के बारे में जानकारी होती है.
      • "संग्रहित करें" डिफ़ॉल्ट रूप से, इन फ़ील्ड में मौजूद होता है:
        • 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 में "संग्रह" टाइप का इस्तेमाल किया गया हो.

Bazel Central Registry

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

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

सामान्य इंडेक्स रजिस्ट्री के फ़ॉर्मैट को फ़ॉलो करने के साथ-साथ, बीसीआर में हर मॉड्यूल वर्शन (/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" के लिए बताए गए हैं. इस डिपेंडेंसी ग्राफ़ को फ़ाइनल किए जाने के बाद, D 1.3 पर B 1.2 का bazel_dep हो सकता है, लेकिन C की वजह से D 1.4 में अपग्रेड हो गया. इसके बाद, "maven" एक्सटेंशन चलता है. साथ ही, वह सभी maven.* टैग को पढ़ लेता है. इससे यह तय किया जाता है कि कौनसा डेटा स्टोर करना है. इसी तरह "कार्गो" एक्सटेंशन के लिए.

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

एक्सटेंशन को 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 के साथ, आपके पास अपने मॉड्यूल के स्कोप में उनका नाम बदलने का विकल्प है, जैसे कि यहां "गुआवा".

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

मॉड्यूल एक्सटेंशन, रिपो नियमों की तरह ही बताए जाते हैं. इसके लिए, module_extension फ़ंक्शन का इस्तेमाल किया जाता है. दोनों में लागू करने का फ़ंक्शन होता है; हालांकि, स्टोर करने के नियमों में कई एट्रिब्यूट होते हैं, लेकिन मॉड्यूल एक्सटेंशन में कई tag_class होते हैं. दोनों में से हर एक एट्रिब्यूट की संख्या होती है. टैग क्लास, इस एक्सटेंशन में इस्तेमाल किए जाने वाले टैग के लिए स्कीमा तय करती हैं. ऊपर दिए गए काल्पनिक "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.* टैग दिखाता है. इसके बाद, Maven से संपर्क करने और रिज़ॉल्यूशन लागू करने के लिए, हमने CLI यूटिलिटी कोर्स को शुरू किया. आखिर में, हम maven_single_jar रिपो नियम का इस्तेमाल करके कई डेटा रिपॉज़िटरी (डेटा स्टोर करने की जगह) बनाने के लिए, रिज़ॉल्यूशन नतीजे का इस्तेमाल करते हैं.