Bazel मॉड्यूल, एक ऐसा Bazel प्रोजेक्ट है जिसमें कई वर्शन हो सकते हैं. इनमें से हर वर्शन, उन अन्य मॉड्यूल के बारे में मेटाडेटा पब्लिश करता है जिन पर वह निर्भर करता है. यह दूसरे डिपेंडेंसी मैनेजमेंट सिस्टम में, जाने-पहचाने कॉन्सेप्ट से मिलता-जुलता है. जैसे, Maven आर्टफ़ैक्ट, एनपीएम पैकेज, Go मॉड्यूल या कार्गो क्रेट.
मॉड्यूल के रेपो रूट में (WORKSPACE
फ़ाइल के बगल में) एक 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
).
इस वजह से, Bzlmod में SemVer स्पेसिफ़िकेशन का ज़्यादा आसान वर्शन अपनाया गया है. इनमें ये अंतर शामिल हैं:
- SemVer का मानना है कि वर्शन के "रिलीज़" वाले हिस्से में तीन सेगमेंट होने चाहिए:
MAJOR.MINOR.PATCH
. Basel में, इस शर्त को ढीला कर दिया गया है, ताकि कितने भी सेगमेंट की अनुमति दी जा सके. - SemVer में, "रिलीज़" वाले हिस्से के हर सेगमेंट में सिर्फ़ अंक होने चाहिए. Bazel में, अक्षरों को भी शामिल करने के लिए इस शर्त को कम किया गया है. साथ ही, तुलना के सेमेटिक्स, "प्री-रिलीज़" वाले हिस्से में मौजूद "आइडेंटिफ़ायर" से मैच करते हैं.
- इसके अलावा, मेजर, माइनर, और पैच वर्शन की बढ़ोतरी के सिमेंटिक्स को लागू नहीं किया जाता. हालांकि, हम पुराने सिस्टम के साथ काम करने की सुविधा के बारे में जानकारी देने के लिए, कंपैटिबिलिटी लेवल का इस्तेमाल करते हैं.
SemVer का कोई भी मान्य वर्शन, Basel मॉड्यूल का एक मान्य वर्शन है. इसके अलावा, दो SemVer वर्शन a
और b
a < b
की तुलना सिर्फ़ तब करते हैं, जब उनकी तुलना बज़ल मॉड्यूल वर्शन के रूप में की जाती है और दोनों वर्शन एक ही होते हैं.
वर्शन चुनना
डायमंड डिपेंडेंसी की समस्या पर विचार करें. यह वर्शन वाले डिपेंडेंसी मैनेजमेंट स्पेस में एक आम समस्या है. मान लें कि आपके पास डिपेंडेंसी ग्राफ़ है:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
D
के किस वर्शन का इस्तेमाल किया जाना चाहिए? इस सवाल का हल करने के लिए, Bzlmod ने Go मॉड्यूल सिस्टम में उपलब्ध कम से कम वर्शन चुनने की सुविधा (एमवीएस) एल्गोरिदम का इस्तेमाल किया है. MVS यह मानता है कि किसी मॉड्यूल के सभी नए वर्शन, पुराने वर्शन के साथ काम करते हैं. इसलिए, वह किसी भी डिपेंडेंट (हमारे उदाहरण में D 1.1
) के ज़रिए बताए गए सबसे नए वर्शन को चुनता है. इसे "कम से कम" इसलिए कहा जाता है, क्योंकि D 1.1
सबसे पुराना वर्शन है जो हमारी ज़रूरी शर्तों को पूरा कर सकता है. भले ही, D 1.2
या नया वर्शन मौजूद हो, हम उन्हें नहीं चुनते. एमवीएस का इस्तेमाल करने से, वर्शन चुनने की ऐसी प्रोसेस बनती है जो हाई-फ़िडेलिटी और फिर से बनाई जा सकने वाली होती है.
यांक किए गए वर्शन
रजिस्ट्री, कुछ वर्शन को हटाया गया के तौर पर दिखा सकती है. ऐसा तब किया जाता है, जब उन वर्शन का इस्तेमाल नहीं किया जाना चाहिए (जैसे, सुरक्षा से जुड़ी समस्याओं की वजह से). किसी मॉड्यूल के यैंक किए गए वर्शन को चुनते समय Baज़र एक गड़बड़ी दिखाता है. इस गड़बड़ी को ठीक करने के लिए, किसी ऐसे नए वर्शन पर अपग्रेड करें जिसे हटाया नहीं गया है या हटाए गए वर्शन को अनुमति देने के लिए, --allow_yanked_versions
फ़्लैग का इस्तेमाल करें.
काम करने का लेवल
Go में, पुराने सिस्टम के साथ काम करने की सुविधा के बारे में MVS का अनुमान सही होता है. ऐसा इसलिए होता है, क्योंकि यह किसी मॉड्यूल के पुराने सिस्टम के साथ काम न करने वाले वर्शन को अलग मॉड्यूल के तौर पर इस्तेमाल करता है. SemVer के हिसाब से, इसका मतलब है कि A 1.x
और A 2.x
को अलग-अलग मॉड्यूल माना जाता है. साथ ही, ये दोनों एक साथ, डिपेंडेंसी ग्राफ़ में मौजूद हो सकते हैं. ऐसा, Go में पैकेज पाथ में मेजर वर्शन को कोड में बदलकर किया जाता है, ताकि संकलन के समय या लिंक करने के समय कोई विरोध न हो.
हालांकि, Bazel ऐसी कोई गारंटी नहीं दे सकता. इसलिए, पुराने वर्शन के साथ काम न करने वाले वर्शन का पता लगाने के लिए, उसे "मुख्य वर्शन" के नंबर की ज़रूरत होती है. इस नंबर को काम करने का लेवल कहा जाता है. यह हर मॉड्यूल के वर्शन के module()
डायरेक्टिव में बताया जाता है. इस जानकारी की मदद से, Bazel गड़बड़ी का मैसेज दिखा सकता है. ऐसा तब होता है, जब उसे पता चलता है कि रिज़ॉल्व किए गए डिपेंडेंसी ग्राफ़ में, एक ही मॉड्यूल के अलग-अलग वर्शन मौजूद हैं और उनका काम करने का तरीका अलग-अलग है.
बदली गई कीमत
बेज़ल मॉड्यूल रिज़ॉल्यूशन के व्यवहार में बदलाव करने के लिए, MODULE.bazel
फ़ाइल में बदलावों के बारे में बताएं. सिर्फ़ रूट मॉड्यूल के बदलाव लागू होते हैं — अगर किसी मॉड्यूल का इस्तेमाल डिपेंडेंसी के तौर पर किया जाता है, तो उसके बदलावों को अनदेखा कर दिया जाता है.
हर बदलाव, किसी मॉड्यूल के नाम के लिए तय किया जाता है. इससे डिपेंडेंसी ग्राफ़ में उसके सभी वर्शन पर असर पड़ता है. हालांकि, सिर्फ़ रूट मॉड्यूल के ओवरराइड का असर पड़ता है, लेकिन वे ट्रांज़िटिव डिपेंडेंसी के लिए हो सकते हैं जिन पर रूट मॉड्यूल सीधे तौर पर निर्भर नहीं होता.
एक वर्शन को ओवरराइड करना
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.5
और2.0
को अनुमति देने वाले कई वर्शन को बदलने पर गड़बड़ी होती है, क्योंकि1.7
के पास उसी वर्शन के साथ काम करने वाले किसी भी वर्शन को अपग्रेड करने की सुविधा नहीं है.1.9
और2.0
को अनुमति देने वाले कई वर्शन के बदलाव से गड़बड़ी होती है, क्योंकि रिज़ॉल्यूशन से पहले1.9
, डिपेंडेंसी ग्राफ़ में मौजूद नहीं होता.
इसके अलावा, उपयोगकर्ता registry
एट्रिब्यूट का इस्तेमाल करके भी रजिस्ट्री को बदल सकते हैं. यह उसी तरह है जैसे एक वर्शन को बदला जाता है.
रजिस्ट्री के अलावा अन्य ओवरराइड
रजिस्ट्री के बाहर के बदलाव, वर्शन रिज़ॉल्यूशन से किसी मॉड्यूल को पूरी तरह हटा देते हैं. Basel, इन MODULE.bazel
फ़ाइलों के लिए रजिस्ट्री से अनुरोध नहीं करता. इसके बजाय, वह रेपो से अनुरोध करता है.
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
का इस्तेमाल करें.यह इंस्टेंस, रिपॉज़िटरी के नाम से दी गई लेबल स्ट्रिंग से बनाया जाता है. उदाहरण के लिए,
Label("@bazel_skylib").repo_name
.
* रनफ़ाइल खोजते समय,
$(rlocationpath ...)
या
@bazel_tools//tools/{bash,cpp,java}/runfiles
में किसी रनफ़ाइल लाइब्रेरी का इस्तेमाल करें या
@rules_foo//foo/runfiles
में, नियमों की सूची rules_foo
के लिए
इस्तेमाल करें.
* किसी बाहरी टूल, जैसे कि आईडीई या भाषा के सर्वर से Bazel का इस्तेमाल करते समय, bazel mod dump_repo_mapping
कमांड का इस्तेमाल करके, किसी रिपॉज़िटरी के दिए गए सेट के लिए, सामान्य नामों से कैननिकल नामों पर मैपिंग पाएं.
मॉड्यूल एक्सटेंशन, मॉड्यूल के दिखने वाले दायरे में अतिरिक्त रिपॉज़िटरी भी जोड़ सकते हैं.