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 registry से किसी भी डिपेंडेंसी की 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 फ़्लैग का इस्तेमाल करके, यैंक किए गए वर्शन को साफ़ तौर पर अनुमति दी जा सकती है.

बदली गई कीमत

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

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

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

The 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, नॉन-रजिस्ट्री ओवरराइड के इन टाइप के साथ काम करता है:

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

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