Bazel मॉड्यूल

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

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

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

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

मॉड्यूल रिज़ॉल्यूशन के लिए, Bazel सबसे पहले रूट मॉड्यूल की MODULE.bazel फ़ाइल को पढ़ता है. इसके बाद, यह किसी भी डिपेंडेंसी की MODULE.bazel फ़ाइल के लिए, Bazel रजिस्ट्री से बार-बार अनुरोध करता है. ऐसा तब तक किया जाता है, जब तक कि इसे पूरा डिपेंडेंसी ग्राफ़ नहीं मिल जाता.

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

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

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

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

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

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

Yank किए गए वर्शन

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

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

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

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

बदली गई कीमत

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

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

एक वर्शन को ओवरराइड करना

single_version_override कई कामों के लिए इस्तेमाल किया जाता है:

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

ये सभी एट्रिब्यूट ज़रूरी नहीं हैं. इन्हें एक-दूसरे के साथ मिलाकर इस्तेमाल किया जा सकता है.

एक से ज़्यादा वर्शन को ओवरराइड करना

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

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

उदाहरण के लिए, अगर रिज़ॉल्यूशन से पहले डिपेंडेंसी ग्राफ़ में वर्शन 1.1, 1.3, 1.5, 1.7, और 2.0 मौजूद हैं और मुख्य वर्शन, कंपैटिबिलिटी लेवल है, तो:

  • एक से ज़्यादा वर्शन को बदलने की सुविधा का इस्तेमाल करके, 1.3, 1.7, और 2.0 को बदला जा सकता है. इससे 1.1 को 1.3 में, 1.5 को 1.7 में अपग्रेड किया जा सकता है. साथ ही, अन्य वर्शन में कोई बदलाव नहीं होता.
  • एक से ज़्यादा वर्शन को बदलने की सुविधा का इस्तेमाल करने पर, 1.5 और 2.0 में गड़बड़ी होती है. ऐसा इसलिए होता है, क्योंकि 1.7 के पास अपग्रेड करने के लिए, उसी लेवल का कोई नया वर्शन नहीं होता.
  • एक से ज़्यादा वर्शन को बदलने की सुविधा का इस्तेमाल करके 1.9 और 2.0 को बदलने पर गड़बड़ी होती है. ऐसा इसलिए होता है, क्योंकि 1.9, रिज़ॉल्यूशन से पहले डिपेंडेंसी ग्राफ़ में मौजूद नहीं होता.

इसके अलावा, उपयोगकर्ता registry एट्रिब्यूट का इस्तेमाल करके भी रजिस्ट्री को बदल सकते हैं. यह सिंगल-वर्शन ओवरराइड की तरह ही काम करता है.

रजिस्ट्री के बाहर के बदलाव

नॉन-रजिस्ट्री ओवरराइड, वर्शन रिज़ॉल्यूशन से किसी मॉड्यूल को पूरी तरह हटा देते हैं. Bazel, इन MODULE.bazel फ़ाइलों का अनुरोध किसी रजिस्ट्री से नहीं करता, बल्कि सीधे तौर पर रिपो से करता है.

Bazel, रजिस्ट्री के बाहर मौजूद इन फ़ाइलों को बदलने की सुविधा देता है:

रिपॉज़िटरी के नाम और स्ट्रिक्ट डिपेंडेंसी

किसी मॉड्यूल को बैक करने वाले रेपो का कैननिकल नाम module_name~version होता है. उदाहरण के लिए, bazel_skylib~1.0.3. अगर किसी मॉड्यूल के लिए, नॉन-रजिस्ट्री ओवरराइड का इस्तेमाल किया गया है, तो version वाले हिस्से को override स्ट्रिंग से बदलें. ध्यान दें कि कैननिकल नाम का फ़ॉर्मैट ऐसा एपीआई नहीं है जिस पर आपको भरोसा करना चाहिए. इसमें किसी भी समय बदलाव हो सकता है.

किसी मॉड्यूल को सपोर्ट करने वाले रेपो का दिखने वाला नाम, डिफ़ॉल्ट रूप से उसके मॉड्यूल का नाम होता है. हालांकि, अगर bazel_dep डायरेक्टिव के repo_name एट्रिब्यूट में कोई दूसरा नाम दिया गया है, तो वह नाम दिखेगा. ध्यान दें कि इसका मतलब है कि कोई मॉड्यूल सिर्फ़ अपनी डायरेक्ट डिपेंडेंसी ढूंढ सकता है. इससे ट्रांज़िटिव डिपेंडेंसी में बदलाव होने की वजह से, अनजाने में होने वाली गड़बड़ियों को रोकने में मदद मिलती है.

मॉड्यूल एक्सटेंशन की मदद से, मॉड्यूल के स्कोप में अतिरिक्त रिपॉज़िटरी भी जोड़ी जा सकती हैं.