Bazel लॉकफ़ाइल

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

Lockfile Generation

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

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

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

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

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

Lockfile के फ़ायदे

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

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

  • तेज़ी से इंक्रीमेंटल रिज़ॉल्यूशन. लॉकफ़ाइल की मदद से, 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 का इस्तेमाल करें. साथ ही, वर्शन कंट्रोल में .bazelversion फ़ाइल शामिल करें. यह फ़ाइल, लॉकफ़ाइल से जुड़े Bazel वर्शन के बारे में बताती है. Bazel, आपके बिल्ड की डिपेंडेंसी है. इसलिए, लॉकफ़ाइल Bazel के वर्शन के हिसाब से होती है. साथ ही, पिछले वर्शन के साथ काम करने वाले Bazel के रिलीज़ वर्शन के बीच भी इसमें बदलाव होता है. bazelisk का इस्तेमाल करने से यह पक्का किया जा सकता है कि सभी डेवलपर, लॉकफ़ाइल से मेल खाने वाले Bazel वर्शन का इस्तेमाल कर रहे हैं.

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

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

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

अपने-आप हल होने की सुविधा

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

अपनी 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 चलाएं.