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")

फ़ाइलों में उपलब्ध डायरेक्टिव की MODULE.bazel पूरी सूची देखें.

मॉड्यूल रिज़ॉल्यूशन के लिए, 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, इन नॉन-रजिस्ट्री ओवरराइड के साथ काम करता है:

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

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