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 merge ड्राइवर उपलब्ध कराता है.

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