Bazel मॉड्यूल

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

किसी मॉड्यूल के लिए, उसकी रिपॉज़िटरी के रूट में 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 फ़ाइलों में उपलब्ध डायरेक्टिव की पूरी सूची देखें.

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

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

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

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

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

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

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

आखिर में, मॉड्यूल के वर्शन के बारे में ज़्यादा जानने के लिए, MODULE.bazel अक्सर पूछे जाने वाले सवाल देखें.

वर्शन चुनना

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

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

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

Yank किए गए वर्शन

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

बदली गई कीमत

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

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

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

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

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

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

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

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

अगर डिपेंडेंसी ग्राफ़ में एक ही मॉड्यूल के कई वर्शन मौजूद हैं, तो 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.9 और 2.0 को बदलने पर गड़बड़ी होती है. ऐसा इसलिए होता है, क्योंकि समाधान से पहले 1.9, डिपेंडेंसी ग्राफ़ में मौजूद नहीं होता है.

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

रजिस्ट्री के बाहर के ओवरराइड

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

Bazel, रजिस्ट्री से बाहर के इन ओवरराइड को सपोर्ट करता है:

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

उन रिपॉज़िटरी को तय करें जो Bazel मॉड्यूल नहीं हैं

bazel_dep की मदद से, ऐसे रेपो तय किए जा सकते हैं जो अन्य Bazel मॉड्यूल को दिखाते हैं. कभी-कभी, ऐसी रिपॉज़िटरी को तय करने की ज़रूरत होती है जो Bazel मॉड्यूल को नहीं दिखाती है. उदाहरण के लिए, ऐसी रिपॉज़िटरी जिसमें डेटा के तौर पर पढ़ी जाने वाली सादी JSON फ़ाइल शामिल हो.

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

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

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

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

किसी मॉड्यूल को बैक करने वाली रिपॉज़िटरी का कैननिकल नाम, module_name+version (उदाहरण के लिए, bazel_skylib+1.0.3) या module_name+ (उदाहरण के लिए, bazel_features+) होता है. यह इस बात पर निर्भर करता है कि पूरे डिपेंडेंसी ग्राफ़ में मॉड्यूल के कई वर्शन हैं या नहीं (multiple_version_override देखें). ध्यान दें कि कैननिकल नाम का फ़ॉर्मैट ऐसा एपीआई नहीं है जिस पर आपको भरोसा करना चाहिए. साथ ही, इसमें किसी भी समय बदलाव किया जा सकता है. कैननिकल नाम को हार्ड-कोड करने के बजाय, इसे सीधे Bazel से पाने के लिए, इस्तेमाल किए जा सकने वाले तरीके का इस्तेमाल करें:

  • BUILD और .bzl फ़ाइलों में, Label इंस्टेंस पर Label.repo_name का इस्तेमाल करें.यह इंस्टेंस, रिपॉज़िटरी के नाम से दिए गए लेबल स्ट्रिंग से बनाया गया है. उदाहरण के लिए, Label("@bazel_skylib").repo_name.
  • रनफ़ाइलें ढूंढते समय, $(rlocationpath ...) का इस्तेमाल करें. इसके अलावा, @bazel_tools//tools/{bash,cpp,java}/runfiles में मौजूद रनफ़ाइल लाइब्रेरी में से किसी एक का इस्तेमाल करें. अगर आपको rules_foo के लिए रनफ़ाइलें ढूंढनी हैं, तो @rules_foo//foo/runfiles में मौजूद रनफ़ाइल लाइब्रेरी में से किसी एक का इस्तेमाल करें.
  • जब किसी बाहरी टूल, जैसे कि IDE या भाषा सर्वर से Bazel के साथ इंटरैक्ट किया जाता है, तो bazel mod dump_repo_mapping कमांड का इस्तेमाल करें. इससे आपको रिपॉज़िटरी के किसी सेट के लिए, दिखने वाले नामों से कैननिकल नामों की मैपिंग मिलेगी.

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