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 में, "रिलीज़" वाले हिस्से के हर सेगमेंट में सिर्फ़ अंक होने चाहिए. Bazel में, अक्षरों को भी अनुमति देने के लिए इसे कम किया गया है. साथ ही, तुलना के सिमैंटिक, "prerelease" वाले हिस्से में मौजूद "पहचानकर्ताओं" से मेल खाते हैं.
  • इसके अलावा, मेजर, माइनर, और पैच वर्शन के सिमैंटिक को लागू नहीं किया जाता. हालांकि, हम पुराने सिस्टम के साथ काम करने की सुविधा को कैसे दिखाते हैं, इस बारे में ज़्यादा जानने के लिए संगतता का लेवल देखें.

कोई भी मान्य 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 फ़्लैग का इस्तेमाल करें.

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

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

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

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