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

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

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

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

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

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

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

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

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

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

इसलिए, Bazel को SemVer के मेजर वर्शन नंबर के बराबर नंबर की ज़रूरत होती है, ताकि पुराने सिस्टम के साथ काम न करने वाले ("ब्रेकिंग") वर्शन का पता लगाया जा सके. इस नंबर को कंपैटबिलिटी लेवल कहा जाता है. इसे हर मॉड्यूल वर्शन, अपने module() डायरेक्टिव में तय करता है. इस जानकारी की मदद से, 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.bazel में वर्शन की वैल्यू सेट करने से, कुछ समस्याएं हो सकती हैं. ऐसा तब होता है, जब मॉड्यूल को नॉन-रजिस्ट्री ओवरराइड से ओवरराइड किया जा रहा हो. इस बारे में ज़्यादा जानने के लिए, अक्सर पूछे जाने वाले सवाल देखें.MODULE.bazel

ऐसे रेपो तय करना जो Bazel मॉड्यूल नहीं हैं

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

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

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

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

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

किसी मॉड्यूल को बैकअप देने वाले रेपो का कैननिकल नाम, 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 ...) या रनफ़ाइल लाइब्रेरी में से किसी एक का इस्तेमाल करें. इसके अलावा, नियमसेट rules_foo के लिए, @rules_foo//foo/runfiles में मौजूद लाइब्रेरी का इस्तेमाल करें.@bazel_tools//tools/{bash,cpp,java}/runfiles
  • जब किसी बाहरी टूल से Bazel के साथ इंटरैक्ट किया जाता है, जैसे कि IDE या लैंग्वेज सर्वर, तो दिए गए रिपॉज़िटरी के सेट के लिए, नामों से कैननिकल नामों की मैपिंग पाने के लिए, bazel mod dump_repo_mapping कमांड का इस्तेमाल करें.

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