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