एक्सटर्नल डिपेंडेंसी के बारे में खास जानकारी

Bazel, एक्सटर्नल डिपेंडेंसी, सोर्स फ़ाइलों (टेक्स्ट और बाइनरी, दोनों) के साथ काम करता है. इनका इस्तेमाल आपके बिल्ड में किया जाता है. ये फ़ाइलें, आपके वर्कस्पेस से नहीं होती हैं. उदाहरण के लिए, ये GitHub रिपॉज़िटरी में होस्ट किया गया कोई नियम सेट, Maven आर्टफ़ैक्ट या आपके मौजूदा वर्कस्पेस से बाहर, आपकी लोकल मशीन पर मौजूद कोई डायरेक्ट्री हो सकती हैं.

Bazel 6.0 से, Bazel के साथ एक्सटर्नल डिपेंडेंसी मैनेज करने के दो तरीके हैं: पहला, रिपॉज़िटरी पर फ़ोकस करने वाला पुराना WORKSPACE सिस्टम और दूसरा, मॉड्यूल पर फ़ोकस करने वाला नया MODULE.bazel सिस्टम. इसे Bzlmod कोडनेम दिया गया है, और यह --enable_bzlmod फ़्लैग के साथ काम करता है. इन दोनों सिस्टम का इस्तेमाल एक साथ किया जा सकता है. हालांकि, Bazel की आने वाली रिलीज़ में, WORKSPACE सिस्टम की जगह Bzlmod का इस्तेमाल किया जाएगा. माइग्रेट करने का तरीका जानने के लिए, Bzlmod माइग्रेशन गाइड देखें.

इस दस्तावेज़ में, Bazel में एक्सटर्नल डिपेंडेंसी मैनेजमेंट से जुड़े कॉन्सेप्ट के बारे में बताया गया है. इसके बाद, दोनों सिस्टम के बारे में ज़्यादा जानकारी दी गई है.

कॉन्सेप्ट

रिपॉज़िटरी

यह डायरेक्ट्री ट्री होती है. इसकी रूट डायरेक्ट्री में, बाउंड्री मार्कर फ़ाइल होती है. इसमें सोर्स फ़ाइलें होती हैं, जिनका इस्तेमाल Bazel बिल्ड में किया जा सकता है. इसे अक्सर सिर्फ़ रिपो कहा जाता है.

रिपो बाउंड्री मार्कर फ़ाइल, MODULE.bazel (इससे पता चलता है कि यह रिपो Bazel मॉड्यूल को दिखाता है), REPO.bazel (नीचे देखें) या पुराने कॉन्टेक्स्ट में, WORKSPACE या WORKSPACE.bazel हो सकती है. कोई भी रिपो बाउंड्री मार्कर फ़ाइल, रिपो की बाउंड्री को दिखाएगी. एक डायरेक्ट्री में ऐसी कई फ़ाइलें मौजूद हो सकती हैं.

मुख्य रिपॉज़िटरी

वह रिपॉज़िटरी जिसमें Bazel का मौजूदा कमांड चलाया जा रहा है.

मुख्य रिपॉज़िटरी की रूट डायरेक्ट्री को वर्कस्पेस रूट भी कहा जाता है.

Workspace

यह वह एनवायरमेंट है जिसे एक ही मुख्य रिपॉज़िटरी में चलाए गए Bazel के सभी कमांड शेयर करते हैं. इसमें मुख्य रिपो और तय की गई सभी एक्सटर्नल रिपो शामिल होती हैं.

ध्यान दें कि पहले "रिपॉज़िटरी" और "वर्कस्पेस" के कॉन्सेप्ट को एक ही माना जाता था. "वर्कस्पेस" शब्द का इस्तेमाल अक्सर मुख्य रिपॉज़िटरी के लिए किया जाता था. कभी-कभी इसे "रिपॉज़िटरी" के सिननिम के तौर पर भी इस्तेमाल किया जाता था.

कैननिकल रिपॉज़िटरी का नाम

वह कैननिकल नाम जिससे किसी रिपॉज़िटरी को ऐक्सेस किया जा सकता है. किसी वर्कस्पेस के कॉन्टेक्स्ट में, हर रिपॉज़िटरी का एक ही कैननिकल नाम होता है. किसी रिपो में मौजूद टारगेट जिसका कैननिकल नाम canonical_name है, उसे @@canonical_name//package:target लेबल से ऐक्सेस किया जा सकता है. ध्यान दें कि इसमें दो @ का इस्तेमाल किया गया है.

मुख्य रिपॉज़िटरी का कैननिकल नाम हमेशा खाली स्ट्रिंग होता है.

रिपॉज़िटरी का नाम

वह नाम जिससे किसी दूसरी रिपो के कॉन्टेक्स्ट में, किसी रिपॉज़िटरी को ऐक्सेस किया जा सकता है. इसे किसी रिपो का "निकनेम" माना जा सकता है: alice रिपो के कॉन्टेक्स्ट में, michael कैननिकल नाम वाली रिपो का नाम mike हो सकता है. वहीं, bob रिपो के कॉन्टेक्स्ट में, इसका नाम mickey हो सकता है. इस मामले में, michael में मौजूद टारगेट को alice के कॉन्टेक्स्ट में, लेबल @mike//package:target से ऐक्सेस किया जा सकता है. ध्यान दें कि इसमें एक @ का इस्तेमाल किया गया है.

इसके उलट, इसे रिपॉज़िटरी मैपिंग के तौर पर समझा जा सकता है: हर रिपो "रिपो के नाम" से "कैननिकल रिपो के नाम" तक की मैपिंग बनाए रखती है.

रिपॉज़िटरी का नियम

यह रिपॉज़िटरी की डेफ़िनिशन के लिए स्कीमा है. इससे Bazel को पता चलता है कि रिपॉज़िटरी को कैसे तैयार किया जाए. उदाहरण के लिए, यह "किसी यूआरएल से zip आर्काइव डाउनलोड करें और उसे एक्सट्रैक्ट करें" या "किसी Maven आर्टफ़ैक्ट को फ़ेच करें और उसे java_import टारगेट के तौर पर उपलब्ध कराएं" या सिर्फ़ "किसी लोकल डायरेक्ट्री को सिमलंक करें" हो सकता है. हर रिपो को, सही संख्या में आर्ग्युमेंट के साथ रिपो के नियम को कॉल करके तय किया जाता है.

रिपॉज़िटरी के अपने नियम लिखने के तरीके के बारे में ज़्यादा जानने के लिए, रिपॉज़िटरी के नियम देखें.

सबसे आम रिपो के नियम, http_archive और local_repository हैं. http_archive किसी यूआरएल से आर्काइव डाउनलोड करता है और उसे एक्सट्रैक्ट करता है. वहीं, local_repository किसी लोकल डायरेक्ट्री को सिमलंक करता है, जो पहले से ही Bazel रिपॉज़िटरी है.

रिपॉज़िटरी को फ़ेच करना

रिपो के नियम को चलाकर, किसी रिपो को लोकल डिस्क पर उपलब्ध कराने की कार्रवाई. किसी वर्कस्पेस में तय की गई रिपो, फ़ेच किए जाने से पहले लोकल डिस्क पर उपलब्ध नहीं होती हैं.

आम तौर पर, Bazel किसी रिपो को सिर्फ़ तब फ़ेच करता है, जब उसे रिपो से किसी चीज़ की ज़रूरत होती है और रिपो को पहले से फ़ेच नहीं किया गया होता है. अगर रिपो को पहले ही फ़ेच किया जा चुका है, तो Bazel उसे सिर्फ़ तब फिर से फ़ेच करता है, जब उसकी डेफ़िनिशन में बदलाव हुआ हो.

fetch कमांड का इस्तेमाल, किसी रिपॉज़िटरी, टारगेट या किसी भी बिल्ड को पूरा करने के लिए ज़रूरी सभी रिपॉज़िटरी को पहले से फ़ेच करने के लिए किया जा सकता है. इस सुविधा से, --nofetch विकल्प का इस्तेमाल करके ऑफ़लाइन बिल्ड किए जा सकते हैं.

--fetch विकल्प का इस्तेमाल, नेटवर्क ऐक्सेस को मैनेज करने के लिए किया जाता है. इसकी डिफ़ॉल्ट वैल्यू 'सही' होती है. हालांकि, इसे 'गलत' (--nofetch) पर सेट करने पर, कमांड डिपेंडेंसी के किसी भी कैश किए गए वर्शन का इस्तेमाल करेगा. अगर कोई वर्शन मौजूद नहीं है, तो कमांड फ़ेल हो जाएगा.

फ़ेच करने की सुविधा को कंट्रोल करने के बारे में ज़्यादा जानने के लिए, फ़ेच करने के विकल्प देखें.

डायरेक्ट्री का लेआउट

फ़ेच किए जाने के बाद, रिपो को आउटपुट बेस में, external सबडायरेक्ट्री में, उसके कैननिकल नाम से देखा जा सकता है.

canonical_name कैननिकल नाम वाली रिपो का कॉन्टेंट देखने के लिए, यह कमांड चलाएं:

ls $(bazel info output_base)/external/ canonical_name 

REPO.bazel फ़ाइल

REPO.bazel फ़ाइल का इस्तेमाल, डायरेक्ट्री ट्री की सबसे ऊपरी बाउंड्री को मार्क करने के लिए किया जाता है. यह डायरेक्ट्री ट्री, रिपो बनाती है. रिपो बाउंड्री फ़ाइल के तौर पर काम करने के लिए, इसमें कुछ भी शामिल करने की ज़रूरत नहीं होती. हालांकि, इसका इस्तेमाल रिपो में मौजूद सभी बिल्ड टारगेट के लिए कुछ सामान्य एट्रिब्यूट तय करने के लिए भी किया जा सकता है.

REPO.bazel फ़ाइल का सिंटैक्स, BUILD फ़ाइलों जैसा ही होता है. हालांकि, इसमें load स्टेटमेंट काम नहीं करते हैं और सिर्फ़ एक फ़ंक्शन, repo() उपलब्ध होता है. repo() package() फ़ंक्शन में BUILD फ़ाइलों के जैसे ही आर्ग्युमेंट लेता है. package() पैकेज में मौजूद सभी बिल्ड टारगेट के लिए सामान्य एट्रिब्यूट तय करता है. इसी तरह, repo() रिपो में मौजूद सभी बिल्ड टारगेट के लिए सामान्य एट्रिब्यूट तय करता है.

उदाहरण के लिए, अपनी रिपो में मौजूद सभी टारगेट के लिए सामान्य लाइसेंस तय करने के लिए, REPO.bazel फ़ाइल में यह जानकारी शामिल करें:

repo(
    default_package_metadata = ["//:my_license"],
)

Bzlmod की मदद से एक्सटर्नल डिपेंडेंसी मैनेज करना

एक्सटर्नल डिपेंडेंसी का नया सबसिस्टम, Bzlmod, रिपो की डेफ़िनिशन के साथ सीधे काम नहीं करता. इसके बजाय, यह मॉड्यूल से डिपेंडेंसी ग्राफ़ बनाता है, ग्राफ़ के ऊपर एक्सटेंशन चलाता है, और इसके मुताबिक रिपो तय करता है.

एक Bazel मॉड्यूल, Bazel का ऐसा प्रोजेक्ट है जिसके कई वर्शन हो सकते हैं. इनमें से हर वर्शन, उन अन्य मॉड्यूल के बारे में मेटाडेटा पब्लिश करता है जिन पर वह निर्भर करता है. किसी मॉड्यूल की रिपो रूट डायरेक्ट्री में, 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")

किसी मॉड्यूल को सिर्फ़ अपनी डायरेक्ट डिपेंडेंसी की सूची बनानी चाहिए. Bzlmod, Bazel रजिस्ट्री में इनकी जानकारी ढूंढता है. डिफ़ॉल्ट रूप से, यह Bazel Central Registry होती है. रजिस्ट्री, डिपेंडेंसी की MODULE.bazel फ़ाइलें उपलब्ध कराती है. इससे Bazel, वर्शन रिज़ॉल्यूशन करने से पहले, ट्रांज़िटिव डिपेंडेंसी का पूरा ग्राफ़ ढूंढ पाता है.

वर्शन रिज़ॉल्यूशन के बाद, जिसमें हर मॉड्यूल के लिए एक वर्शन चुना जाता है, Bazel रजिस्ट्री से फिर से सलाह लेता है, ताकि उसे पता चल सके कि हर मॉड्यूल के लिए रिपो कैसे तय की जाए. ज़्यादातर मामलों में, http_archive का इस्तेमाल किया जाता है.

मॉड्यूल, टैग नाम का डेटा भी तय कर सकते हैं. इनका इस्तेमाल, मॉड्यूल रिज़ॉल्यूशन के बाद मॉड्यूल एक्सटेंशन करते हैं, ताकि अतिरिक्त रिपो तय की जा सकें. इन एक्सटेंशन में, रिपो के नियमों जैसी ही सुविधाएं होती हैं. इससे वे फ़ाइल I/O और नेटवर्क अनुरोध भेजने जैसी कार्रवाइयां कर पाते हैं. इनकी मदद से, Bazel मॉड्यूल से बने डिपेंडेंसी ग्राफ़ का पालन करते हुए, Bazel अन्य पैकेज मैनेजमेंट सिस्टम के साथ इंटरैक्ट कर पाता है.

WORKSPACE की मदद से रिपो तय करना

पहले, WORKSPACE (या WORKSPACE.bazel) फ़ाइल में रिपो तय करके, एक्सटर्नल डिपेंडेंसी मैनेज की जा सकती थीं. इस फ़ाइल का सिंटैक्स, BUILD फ़ाइलों जैसा ही होता है. इसमें बिल्ड के नियमों के बजाय, रिपो के नियमों का इस्तेमाल किया जाता है.

WORKSPACE फ़ाइल में, http_archive रिपो के नियम का इस्तेमाल करने का उदाहरण यहां दिया गया है:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "foo",
    urls = ["https://example.com/foo.zip"],
    sha256 = "c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96",
)

इस स्निपेट में, foo कैननिकल नाम वाली रिपो तय की गई है. WORKSPACE सिस्टम में, डिफ़ॉल्ट रूप से, किसी रिपो का कैननिकल नाम, अन्य सभी रिपो के लिए उसका नाम भी होता है.

फ़ाइलों में उपलब्ध फ़ंक्शन की पूरी सूची देखें WORKSPACE.

WORKSPACE सिस्टम की कमियां

WORKSPACE सिस्टम को लॉन्च किए जाने के बाद से, उपयोगकर्ताओं ने कई समस्याएं बताई हैं. इनमें ये शामिल हैं:

  • Bazel, किसी भी डिपेंडेंसी की WORKSPACE फ़ाइलों का आकलन नहीं करता. इसलिए, डायरेक्ट डिपेंडेंसी के अलावा, सभी ट्रांज़िटिव डिपेंडेंसी को मुख्य रिपो की WORKSPACE फ़ाइल में तय करना होगा.
  • इससे बचने के लिए, प्रोजेक्ट ने "deps.bzl" पैटर्न अपनाया है. इसमें वे एक मैक्रो तय करते हैं. यह मैक्रो, कई रिपो तय करता है. साथ ही, उपयोगकर्ताओं से अपनी WORKSPACE फ़ाइलों में इस मैक्रो को कॉल करने के लिए कहा जाता है.
    • इससे भी समस्याएं होती हैं: मैक्रो, अन्य .bzl फ़ाइलों को load नहीं कर सकते. इसलिए, इन प्रोजेक्ट को अपनी ट्रांज़िटिव डिपेंडेंसी को इस "deps" मैक्रो में तय करना होता है. इसके अलावा, इस समस्या से बचने के लिए, उपयोगकर्ता को कई लेयर वाले "deps" मैक्रो को कॉल करना होता है.
    • Bazel, WORKSPACE फ़ाइल का आकलन क्रम से करता है. इसके अलावा, डिपेंडेंसी को यूआरएल के साथ http_archive का इस्तेमाल करके तय किया जाता है. इसमें वर्शन की कोई जानकारी नहीं होती. इसका मतलब है कि डायमंड डिपेंडेंसी (A, B और C पर निर्भर करता है; B और C, दोनों D के अलग-अलग वर्शन पर निर्भर करते हैं) के मामले में, वर्शन रिज़ॉल्यूशन करने का कोई भरोसेमंद तरीका नहीं है.

WORKSPACE की कमियों की वजह से, Bazel की आने वाली रिलीज़ में, पुराने WORKSPACE सिस्टम की जगह Bzlmod का इस्तेमाल किया जाएगा. Bzlmod पर माइग्रेट करने का तरीका जानने के लिए, कृपया Bzlmod माइग्रेशन गाइड पढ़ें.