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")
मॉड्यूल रिज़ॉल्यूशन के लिए, Bazel सबसे पहले रूट मॉड्यूल की MODULE.bazel फ़ाइल को पढ़ता है. इसके बाद, यह किसी भी डिपेंडेंसी की MODULE.bazel फ़ाइल के लिए, Bazel रजिस्ट्री से बार-बार अनुरोध करता है. ऐसा तब तक किया जाता है, जब तक कि उसे पूरा डिपेंडेंसी ग्राफ़ नहीं मिल जाता.
इसके बाद, Bazel डिफ़ॉल्ट रूप से इस्तेमाल करने के लिए, हर मॉड्यूल का एक वर्शन चुनता है. Bazel, हर मॉड्यूल को एक रेपो के तौर पर दिखाता है. साथ ही, हर रेपो को तय करने का तरीका जानने के लिए, रजिस्ट्री से फिर से सलाह लेता है.
वर्शन का फ़ॉर्मैट
Bazel का एक बड़ा ईकोसिस्टम है और प्रोजेक्ट में वर्शनिंग की अलग-अलग स्कीम का इस्तेमाल किया जाता है. सबसे ज़्यादा लोकप्रिय वर्शनिंग स्कीम SemVer है. हालांकि, Abseil जैसे कई बड़े प्रोजेक्ट में अलग-अलग स्कीम का इस्तेमाल किया जाता है. Abseil के वर्शन, तारीख के हिसाब से होते हैं. उदाहरण के लिए, 20210324.2).
इस वजह से, Bzlmod SemVer स्पेसिफ़िकेशन के ज़्यादा आसान वर्शन का इस्तेमाल करता है. इन दोनों में ये अंतर हैं:
- SemVer के मुताबिक, वर्शन के "रिलीज़" वाले हिस्से में तीन सेगमेंट होने चाहिए:
MAJOR.MINOR.PATCH. Bazel में, इस शर्त को आसान बना दिया गया है, ताकि किसी भी संख्या में सेगमेंट बनाए जा सकें. - SemVer में, "रिलीज़" वाले हिस्से के हर सेगमेंट में सिर्फ़ अंक होने चाहिए. Bazel में, अक्षरों को भी अनुमति देने के लिए इसे कम किया गया है. साथ ही, तुलना के सिमैंटिक, "prerelease" वाले हिस्से में मौजूद "पहचानकर्ताओं" से मेल खाते हैं.
- इसके अलावा, मेजर, माइनर, और पैच वर्शन के सिमैंटिक को लागू नहीं किया जाता. हालांकि, हम पुराने सिस्टम के साथ काम करने की सुविधा को कैसे दिखाते हैं, इस बारे में ज़्यादा जानने के लिए कंपैटिबिलिटी लेवल देखें.
कोई भी मान्य 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 मॉड्यूल सिस्टम में पेश किया गया था. 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 इस तरह की कोई गारंटी नहीं दे सकता. इसलिए, इसे "मेजर वर्शन" नंबर की ज़रूरत होती है, ताकि यह पता लगाया जा सके कि कौनसे वर्शन पिछले वर्शन के साथ काम नहीं करते. इस संख्या को कंपैटिबिलिटी लेवल कहा जाता है. इसे हर मॉड्यूल वर्शन, अपने 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_name~version होता है. उदाहरण के लिए, bazel_skylib~1.0.3. जिन मॉड्यूल के लिए रजिस्ट्री ओवरराइड नहीं किया गया है उनके लिए, version हिस्से को override स्ट्रिंग से बदलें. ध्यान दें कि कैननिकल नाम का फ़ॉर्मैट, ऐसा एपीआई नहीं है जिस पर आपको भरोसा करना चाहिए. इसमें किसी भी समय बदलाव हो सकता है.
किसी मॉड्यूल को बैक करने वाले रेपो का दिखने वाला नाम, सीधे तौर पर उस पर निर्भर रहने वाले मॉड्यूल के नाम के बराबर होता है. हालांकि, ऐसा तब तक होता है, जब तक कि bazel_dep डायरेक्टिव के repo_name एट्रिब्यूट में कोई दूसरा नाम न दिया गया हो. ध्यान दें कि इसका मतलब है कि कोई मॉड्यूल सिर्फ़ अपनी डायरेक्ट डिपेंडेंसी ढूंढ सकता है. इससे ट्रांज़िटिव डिपेंडेंसी में बदलावों की वजह से होने वाली अनचाही रुकावटों से बचा जा सकता है.
मॉड्यूल एक्सटेंशन, मॉड्यूल के दिखने वाले स्कोप में अतिरिक्त रेपो भी जोड़ सकते हैं.