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

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

Bazel का एक बड़ा ईकोसिस्टम है और प्रोजेक्ट में वर्शनिंग की अलग-अलग स्कीम का इस्तेमाल किया जाता है. सबसे ज़्यादा इस्तेमाल किया जाने वाला वर्शनिंग सिस्टम SemVer है. हालांकि, Abseil जैसे कई प्रोजेक्ट में अलग-अलग वर्शनिंग सिस्टम का इस्तेमाल किया जाता है. 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

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

वर्शन का पता लगाने की प्रोसेस, आपकी मशीन पर स्थानीय तौर पर होती है. यह रजिस्ट्री के ज़रिए नहीं होती.

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

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

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

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

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

रजिस्ट्रियां चुनना

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

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

मॉड्यूल एक्सटेंशन की मदद से, मॉड्यूल सिस्टम को बढ़ाया जा सकता है. इसके लिए, ये एक्सटेंशन डिपेंडेंसी ग्राफ़ में मौजूद मॉड्यूल से इनपुट डेटा पढ़ते हैं, डिपेंडेंसी को हल करने के लिए ज़रूरी लॉजिक लागू करते हैं, और आखिर में repo नियमों को कॉल करके repo बनाते हैं. ये आज के 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.* टैग को पढ़ने की अनुमति मिल जाती है. इसके बाद, यह तय किया जाता है कि कौनसी रिपॉज़िटरी बनानी हैं. इसी तरह, "कार्गो" एक्सटेंशन के लिए भी ऐसा ही किया जाता है.

एक्सटेंशन के इस्तेमाल से जुड़ी जानकारी

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

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

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.* टैग दिखते हैं. इसके बाद, हम Maven से संपर्क करने और समस्या हल करने के लिए, CLI यूटिलिटी Coursier का इस्तेमाल करते हैं. आखिर में, हम रिज़ॉल्यूशन के नतीजे का इस्तेमाल करके कई रिपो बनाते हैं. इसके लिए, हम काल्पनिक maven_single_jar रिपो के नियम का इस्तेमाल करते हैं.