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

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

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

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

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

यैंक किए गए वर्शन

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

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

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

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

बदली गई कीमत

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

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

सिंगल-वर्शन ओवरराइड

The 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 स्ट्रिंग से बदलें. ध्यान दें कि कैननिकल नाम का फ़ॉर्मैट कोई ऐसा एपीआई नहीं है जिस पर आपको निर्भर रहना चाहिए. इसमें कभी भी बदलाव किया जा सकता है.

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

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