Bazel लॉकफ़ाइल

Bazel में लॉकफ़ाइल की सुविधा की मदद से, किसी प्रोजेक्ट के लिए ज़रूरी सॉफ़्टवेयर लाइब्रेरी या पैकेज के खास वर्शन या डिपेंडेंसी रिकॉर्ड की जा सकती हैं. यह सुविधा, मॉड्यूल रिज़ॉल्यूशन और एक्सटेंशन के आकलन के नतीजों को सेव करके, ऐसा करती है. लॉकफ़ाइल की मदद से, एक जैसे बिल्ड तैयार किए जा सकते हैं. इससे, डेवलपमेंट के एनवायरमेंट में एकरूपता बनी रहती है. इसके अलावा, लॉकफ़ाइल की मदद से बिल्ड की क्षमता बढ़ती है. ऐसा इसलिए, क्योंकि Bazel, रिज़ॉल्यूशन की प्रोसेस के उन हिस्सों को छोड़ सकता है जिन पर प्रोजेक्ट की डिपेंडेंसी में हुए बदलावों का असर नहीं पड़ता. इसके अलावा, लॉकफ़ाइल की मदद से स्थिरता बेहतर होती है. ऐसा इसलिए, क्योंकि यह बाहरी लाइब्रेरी में अनचाहे अपडेट या बड़े बदलावों को रोकती है. इससे, गड़बड़ियां होने का खतरा कम हो जाता है.

लॉकफ़ाइल जनरेट करना

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

जब प्रोजेक्ट में ऐसे बदलाव होते हैं जिनसे उसकी डिपेंडेंसी पर असर पड़ता है, तो लॉकफ़ाइल अपने-आप अपडेट हो जाती है, ताकि नई स्थिति दिख सके. इससे यह पक्का होता है कि लॉकफ़ाइल, मौजूदा बिल्ड के लिए ज़रूरी डिपेंडेंसी के खास सेट पर फ़ोकस रहे. साथ ही, प्रोजेक्ट की हल की गई डिपेंडेंसी की सटीक जानकारी मिले.

लॉकफ़ाइल का इस्तेमाल करना

लॉकफ़ाइल को फ़्लैग --lockfile_mode से कंट्रोल किया जा सकता है. इससे, प्रोजेक्ट की स्थिति, लॉकफ़ाइल से अलग होने पर, Bazel के व्यवहार को पसंद के मुताबिक बनाया जा सकता है. ये मोड उपलब्ध हैं:

  • update (डिफ़ॉल्ट): जानी-मानी रजिस्ट्री फ़ाइलों के डाउनलोड को स्किप करने और उन एक्सटेंशन का फिर से आकलन न करने के लिए, लॉकफ़ाइल में मौजूद जानकारी का इस्तेमाल करें जिनके नतीजे अब भी अप-टू-डेट हैं. अगर जानकारी मौजूद नहीं है, तो उसे लॉकफ़ाइल में जोड़ा जाएगा. इस मोड में, Bazel, बदली जा सकने वाली जानकारी को रीफ़्रेश करने से भी बचता है. जैसे, उन डिपेंडेंसी के लिए वापस लिए गए वर्शन जिनमें कोई बदलाव नहीं हुआ है.
  • refresh: यह मोड update की तरह काम करता है. हालांकि, इस मोड पर स्विच करने पर, बदली जा सकने वाली जानकारी हमेशा रीफ़्रेश होती है. साथ ही, इस मोड में हर घंटे में एक बार रीफ़्रेश होती है.
  • error: यह मोड update की तरह काम करता है. हालांकि, अगर कोई जानकारी मौजूद नहीं है या पुरानी हो गई है, तो Bazel, गड़बड़ी के साथ फ़ेल हो जाएगा. इस मोड में, रिज़ॉल्यूशन के दौरान लॉकफ़ाइल में कोई बदलाव नहीं होता या नेटवर्क के अनुरोध नहीं किए जाते. मॉड्यूल एक्सटेंशन, जिन्हें reproducible के तौर पर मार्क किया गया है वे अब भी नेटवर्क के अनुरोध कर सकते हैं. हालांकि, उनसे हमेशा एक ही नतीजा मिलने की उम्मीद की जाती है.
  • off: इस मोड में, लॉकफ़ाइल की जांच नहीं की जाती और न ही उसे अपडेट किया जाता है.

लॉकफ़ाइल के फ़ायदे

लॉकफ़ाइल के कई फ़ायदे हैं और इसका इस्तेमाल कई तरीकों से किया जा सकता है:

  • एक जैसे बिल्ड तैयार किए जा सकते हैं. सॉफ़्टवेयर लाइब्रेरी के खास वर्शन या डिपेंडेंसी को कैप्चर करके, लॉकफ़ाइल यह पक्का करती है कि अलग-अलग एनवायरमेंट में और समय के साथ, एक जैसे बिल्ड तैयार किए जा सकें. डेवलपर, अपने प्रोजेक्ट बनाते समय, एक जैसे और अनुमान के मुताबिक नतीजे पा सकते हैं.

  • इंक्रीमेंटल रिज़ॉल्यूशन तेज़ी से किए जा सकते हैं. लॉकफ़ाइल की मदद से, Bazel उन रजिस्ट्री फ़ाइलों को डाउनलोड करने से बच सकता है जिनका इस्तेमाल पहले किसी बिल्ड में किया गया था. इससे, बिल्ड की क्षमता काफ़ी बेहतर होती है. खास तौर पर, उन स्थितियों में जहां रिज़ॉल्यूशन में ज़्यादा समय लग सकता है.

  • स्थिरता और जोखिम कम होता है. लॉकफ़ाइल की मदद से स्थिरता बनाए रखने में मदद मिलती है. ऐसा इसलिए, क्योंकि यह बाहरी लाइब्रेरी में अनचाहे अपडेट या बड़े बदलावों को रोकती है. डिपेंडेंसी को खास वर्शन पर लॉक करके, गड़बड़ियां होने का खतरा कम हो जाता है.

    ऐसा इसलिए होता है, क्योंकि असंगत या बिना जांच किए गए अपडेट की वजह से गड़बड़ियां हो सकती हैं.

छिपी हुई लॉकफ़ाइल

Bazel, "$(bazel info output_base)"/MODULE.bazel.lock पर एक और लॉकफ़ाइल भी रखता है. इस लॉकफ़ाइल के फ़ॉर्मैट और कॉन्टेंट के बारे में साफ़ तौर पर नहीं बताया गया है. इसका इस्तेमाल सिर्फ़ परफ़ॉर्मेंस ऑप्टिमाइज़ेशन के तौर पर किया जाता है. bazel clean --expunge की मदद से, इसे आउटपुट बेस के साथ मिटाया जा सकता है. हालांकि, ऐसा करने की ज़रूरत होना, Bazel या मॉड्यूल एक्सटेंशन में कोई गड़बड़ी है.

लॉकफ़ाइल का कॉन्टेंट

लॉकफ़ाइल में, यह तय करने के लिए सभी ज़रूरी जानकारी होती है कि प्रोजेक्ट की स्थिति में बदलाव हुआ है या नहीं. इसमें, मौजूदा स्थिति में प्रोजेक्ट बनाने का नतीजा भी शामिल होता है. लॉकफ़ाइल के दो मुख्य हिस्से होते हैं:

  1. मॉड्यूल रिज़ॉल्यूशन के लिए इनपुट के तौर पर इस्तेमाल की जाने वाली सभी रिमोट फ़ाइलों के हैश.
  2. हर मॉड्यूल एक्सटेंशन के लिए, लॉकफ़ाइल में वे इनपुट शामिल होते हैं जिन पर इसका असर पड़ता है, इन्हें bzlTransitiveDigest, usagesDigest और अन्य फ़ील्ड के तौर पर दिखाया जाता है. साथ ही, उस एक्सटेंशन को चलाने का आउटपुट भी शामिल होता है. इसे generatedRepoSpecs कहा जाता है.

यहां एक उदाहरण दिया गया है, जिसमें लॉकफ़ाइल का स्ट्रक्चर दिखाया गया है. साथ ही, हर सेक्शन के बारे में जानकारी दी गई है:

{
  "lockFileVersion": 10,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
    "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
    "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
    "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
    "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
    ...
  },
  "selectedYankedVersions": {
    "foo@2.0": "Yanked for demo purposes"
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

रजिस्ट्री फ़ाइल के हैश

registryFileHashes सेक्शन में, मॉड्यूल रिज़ॉल्यूशन के दौरान ऐक्सेस की गई रिमोट रजिस्ट्रियों की सभी फ़ाइलों के हैश शामिल होते हैं. रिज़ॉल्यूशन का एल्गोरिदम, एक जैसे इनपुट दिए जाने पर पूरी तरह से तय होता है. साथ ही, सभी रिमोट इनपुट के हैश किए जाते हैं. इससे, रिज़ॉल्यूशन का नतीजा पूरी तरह से तय होता है. साथ ही, लॉकफ़ाइल में रिमोट जानकारी की डुप्लीकेट कॉपी बनने से बचा जा सकता है. ध्यान दें कि इसके लिए, यह भी रिकॉर्ड करना ज़रूरी है कि किसी खास रजिस्ट्री में कोई मॉड्यूल मौजूद नहीं था, लेकिन कम प्राथमिकता वाली रजिस्ट्री में मौजूद था. उदाहरण के लिए, "नहीं मिला" एंट्री देखें. बदली जा सकने वाली इस जानकारी को bazel mod deps --lockfile_mode=refresh की मदद से अपडेट किया जा सकता है.

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

वापस लिए गए चुने गए वर्शन

selectedYankedVersions सेक्शन में, मॉड्यूल के वे वर्शन शामिल होते हैं जिन्हें मॉड्यूल रिज़ॉल्यूशन के दौरान वापस लिया गया था. आम तौर पर, बिल्ड करने की कोशिश करते समय इससे गड़बड़ी होती है. इसलिए, यह सेक्शन सिर्फ़ तब खाली नहीं होता, जब --allow_yanked_versions या BZLMOD_ALLOW_YANKED_VERSIONS की मदद से, वापस लिए गए वर्शन को साफ़ तौर पर अनुमति दी जाती है.

इस फ़ील्ड की ज़रूरत इसलिए होती है, क्योंकि मॉड्यूल फ़ाइलों के मुकाबले, वापस लिए गए वर्शन की जानकारी बदली जा सकती है. इसलिए, इसे हैश से रेफ़र नहीं किया जा सकता. इस जानकारी को bazel mod deps --lockfile_mode=refresh की मदद से अपडेट किया जा सकता है.

मॉड्यूल एक्सटेंशन

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

अगर कोई एक्सटेंशन, ऑपरेटिंग सिस्टम या आर्किटेक्चर टाइप से अलग है, तो इस सेक्शन में सिर्फ़ एक "सामान्य" एंट्री शामिल होती है. इसके अलावा, इसमें कई एंट्री शामिल होती हैं. इनके नाम, ओएस, आर्किटेक्चर या दोनों के हिसाब से रखे जाते हैं. हर एंट्री, उन खास चीज़ों पर एक्सटेंशन का आकलन करने के नतीजे से जुड़ी होती है.

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

  1. bzlTransitiveDigest, एक्सटेंशन के लागू करने और उसके ज़रिए ट्रांज़िटिव तरीके से लोड की गई .bzl फ़ाइलों का डाइजेस्ट होता है.
  2. usagesDigest, डिपेंडेंसी ग्राफ़ में एक्सटेंशन के इस्तेमाल का डाइजेस्ट होता है. इसमें सभी टैग शामिल होते हैं.
  3. अन्य फ़ील्ड जिनके बारे में नहीं बताया गया है. ये फ़ील्ड, एक्सटेंशन के अन्य इनपुट को ट्रैक करते हैं. जैसे, उन फ़ाइलों या डायरेक्ट्री का कॉन्टेंट जिन्हें वह पढ़ता है या उन एनवायरमेंट वैरिएबल का इस्तेमाल करता है.
  4. generatedRepoSpecs, मौजूदा इनपुट के साथ एक्सटेंशन से बनाई गई रिपॉज़िटरी को एनकोड करते हैं.
  5. ज़रूरी नहीं कि moduleExtensionMetadata फ़ील्ड में, एक्सटेंशन से दिया गया मेटाडेटा शामिल हो. जैसे, बनाई गई कुछ रिपॉज़िटरी को रूट मॉड्यूल से use_repo के ज़रिए इंपोर्ट किया जाना चाहिए या नहीं. इस जानकारी से, bazel mod tidy कमांड काम करता है.

मॉड्यूल एक्सटेंशन, reproducible = True के साथ, वापस किए गए मेटाडेटा को सेट करके, लॉकफ़ाइल में शामिल न होने का विकल्प चुन सकते हैं. ऐसा करके, वे वादा करते हैं कि एक जैसे इनपुट दिए जाने पर, वे हमेशा एक जैसी रिपॉज़िटरी बनाएंगे.

सबसे सही तरीके

लॉकफ़ाइल की सुविधा के ज़्यादा से ज़्यादा फ़ायदे पाने के लिए, यहां दिए गए सबसे सही तरीके अपनाएं:

  • प्रोजेक्ट की डिपेंडेंसी या कॉन्फ़िगरेशन में हुए बदलावों को दिखाने के लिए, लॉकफ़ाइल को समय-समय पर अपडेट करें. इससे यह पक्का होता है कि बाद के बिल्ड, डिपेंडेंसी के सबसे अप-टू-डेट और सटीक सेट पर आधारित हों. सभी एक्सटेंशन को एक साथ लॉक करने के लिए, bazel mod deps --lockfile_mode=update चलाएं.

  • वर्शन कंट्रोल में लॉकफ़ाइल शामिल करें, ताकि साथ मिलकर काम किया जा सके. साथ ही, यह पक्का करें कि टीम के सभी सदस्यों के पास एक ही लॉकफ़ाइल का ऐक्सेस हो. इससे, प्रोजेक्ट में डेवलपमेंट के एनवायरमेंट में एकरूपता बनी रहती है.

  • Bazel चलाने के लिए, bazelisk का इस्तेमाल करें. साथ ही, वर्शन कंट्रोल में a .bazelversion फ़ाइल शामिल करें. इसमें लॉकफ़ाइल के वर्शन के हिसाब से Bazel का वर्शन बताया गया हो. Bazel, आपके बिल्ड की डिपेंडेंसी है. इसलिए, लॉकफ़ाइल, Bazel के वर्शन के हिसाब से होती है. साथ ही, यह Bazel के उन रिलीज़ के बीच भी बदल जाएगी जो पुराने वर्शन के साथ काम करते हैं. bazelisk का इस्तेमाल करने से यह पक्का होता है कि सभी डेवलपर, Bazel के ऐसे वर्शन का इस्तेमाल कर रहे हैं जो लॉकफ़ाइल से मेल खाता है.

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

मर्ज करने से जुड़ी समस्याएं

लॉकफ़ाइल का फ़ॉर्मैट, मर्ज करने से जुड़ी समस्याओं को कम करने के लिए डिज़ाइन किया गया है. हालांकि, ये समस्याएं अब भी हो सकती हैं.

अपने-आप होने वाला रिज़ॉल्यूशन

Bazel, git के लिए एक कस्टम मर्ज ड्राइवर उपलब्ध कराता है. इससे, इन समस्याओं को अपने-आप हल किया जा सकता है.

ड्राइवर सेट अप करने के लिए, अपने git रिपॉज़िटरी के रूट में मौजूद .gitattributes फ़ाइल में यह लाइन जोड़ें:

# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge

इसके बाद, ड्राइवर का इस्तेमाल करने वाले हर डेवलपर को, इन चरणों को फ़ॉलो करके, इसे एक बार रजिस्टर करना होगा:

  1. jq (1.5 या उससे नया वर्शन) इंस्टॉल करें.
  2. ये कमांड दें:
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name   "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"

मैन्युअल तरीके से रिज़ॉल्यूशन

registryFileHashes और selectedYankedVersions फ़ील्ड में, मर्ज करने से जुड़ी सामान्य समस्याओं को सुरक्षित तरीके से हल किया जा सकता है. इसके लिए, समस्या के दोनों पक्षों की सभी एंट्री को सेव रखें.

मर्ज करने से जुड़ी अन्य समस्याओं को मैन्युअल तरीके से हल नहीं किया जाना चाहिए. इसके बजाय:

  1. लॉकफ़ाइल की पिछली स्थिति को वापस लाएं git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock की मदद से.
  2. MODULE.bazel फ़ाइल में मौजूद समस्याओं को हल करें.
  3. लॉकफ़ाइल को अपडेट करने के लिए, bazel mod deps चलाएं.