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

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

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

एक्सटेंशन के इस्तेमाल से जुड़ी जानकारी

एक्सटेंशन, Bazel मॉड्यूल में ही होस्ट किए जाते हैं. किसी मॉड्यूल में एक्सटेंशन का इस्तेमाल करने के लिए, सबसे पहले एक्सटेंशन को होस्ट करने वाले मॉड्यूल में bazel_dep जोड़ें. इसके बाद, इसे स्कोप में लाने के लिए, use_extension बिल्ट-इन फ़ंक्शन को कॉल करें. यहां दिए गए उदाहरण पर ध्यान दें. यह rules_jvm_external मॉड्यूल में तय किए गए "maven" एक्सटेंशन का इस्तेमाल करने के लिए, MODULE.bazel फ़ाइल का एक स्निपेट है:

bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

इससे use_extension की रिटर्न वैल्यू को किसी वैरिएबल से बाइंड किया जाता है. इससे उपयोगकर्ता, एक्सटेंशन के लिए टैग तय करने के लिए डॉट-सिंटैक्स का इस्तेमाल कर सकता है. टैग, एक्सटेंशन की परिभाषा में दिए गए, टैग क्लास के तय किए गए स्कीमा के मुताबिक होने चाहिए. maven.install और maven.artifact टैग के बारे में बताने वाले उदाहरण के लिए:

maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
               artifact = "guava",
               version = "27.0-jre",
               exclusions = ["com.google.j2objc:j2objc-annotations"])

एक्सटेंशन से जनरेट की गई रिपॉज़िटरी को मौजूदा मॉड्यूल के स्कोप में लाने के लिए, use_repo डायरेक्टिव का इस्तेमाल करें.

use_repo(maven, "maven")

किसी एक्सटेंशन से जनरेट किए गए रेपो, उसके एपीआई का हिस्सा होते हैं. इस उदाहरण में, "maven" मॉड्यूल एक्सटेंशन, maven नाम की एक रेपो जनरेट करने का वादा करता है. ऊपर दिए गए एलान की मदद से, एक्सटेंशन @maven//:org_junit_junit जैसे लेबल को सही तरीके से हल करता है, ताकि "maven" एक्सटेंशन से जनरेट किए गए रेपो की ओर इशारा किया जा सके.

एक्सटेंशन की परिभाषा

repo rules की तरह ही, module_extension फ़ंक्शन का इस्तेमाल करके, मॉड्यूल एक्सटेंशन तय किए जा सकते हैं. हालांकि, रिपो के नियमों में कई एट्रिब्यूट होते हैं, जबकि मॉड्यूल एक्सटेंशन में tag_classes होते हैं. इनमें से हर एक में कई एट्रिब्यूट होते हैं. टैग क्लास, इस एक्सटेंशन के इस्तेमाल किए गए टैग के लिए स्कीमा तय करती हैं. उदाहरण के लिए, ऊपर दिए गए "maven" एक्सटेंशन को इस तरह से तय किया जा सकता है:

# @rules_jvm_external//:extensions.bzl

_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
  implementation = _maven_impl,
  tag_classes = {"install": _install, "artifact": _artifact},
)

इन एलान से पता चलता है कि maven.install और maven.artifact टैग को, तय किए गए एट्रिब्यूट स्कीमा का इस्तेमाल करके तय किया जा सकता है.

मॉड्यूल एक्सटेंशन के लागू करने से जुड़े फ़ंक्शन, repo नियमों के लागू करने से जुड़े फ़ंक्शन की तरह ही होते हैं. हालांकि, इन्हें module_ctx ऑब्जेक्ट मिलता है. इससे एक्सटेंशन का इस्तेमाल करने वाले सभी मॉड्यूल और उनसे जुड़े सभी टैग का ऐक्सेस मिलता है. इसके बाद, लागू करने वाला फ़ंक्शन, रिपॉज़िटरी जनरेट करने के लिए रिपॉज़िटरी के नियमों को कॉल करता है.

# @rules_jvm_external//:extensions.bzl

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")  # a repo rule
def _maven_impl(ctx):
  # This is a fake implementation for demonstration purposes only

  # collect artifacts from across the dependency graph
  artifacts = []
  for mod in ctx.modules:
    for install in mod.tags.install:
      artifacts += install.artifacts
    artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]

  # call out to the coursier CLI tool to resolve dependencies
  output = ctx.execute(["coursier", "resolve", artifacts])
  repo_attrs = _process_coursier_output(output)

  # call repo rules to generate repos
  for attrs in repo_attrs:
    http_file(**attrs)
  _generate_hub_repo(name = "maven", repo_attrs)

एक्सटेंशन की पहचान

मॉड्यूल एक्सटेंशन की पहचान, नाम और .bzl फ़ाइल से होती है. यह फ़ाइल, use_extension को किए गए कॉल में दिखती है. यहां दिए गए उदाहरण में, एक्सटेंशन maven की पहचान .bzl फ़ाइल @rules_jvm_external//:extension.bzl और नाम maven से की गई है:

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

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

एक्सटेंशन के लेखक के तौर पर, आपको यह पक्का करना चाहिए कि उपयोगकर्ता सिर्फ़ एक .bzl फ़ाइल से आपके मॉड्यूल एक्सटेंशन का इस्तेमाल करें.

रिपॉज़िटरी के नाम और विज़िबिलिटी

एक्सटेंशन से जनरेट किए गए रिपो के कैननिकल नाम, module_repo_canonical_name+extension_name+repo_name के फ़ॉर्म में होते हैं. ध्यान दें कि कैननिकल नाम का फ़ॉर्मैट, ऐसा एपीआई नहीं है जिस पर आपको निर्भर रहना चाहिए. इसमें कभी भी बदलाव किया जा सकता है.

नाम रखने से जुड़ी इस नीति का मतलब है कि हर एक्सटेंशन का अपना "repo namespace" होता है. दो अलग-अलग एक्सटेंशन, एक ही नाम से repo तय कर सकते हैं. इससे कोई टकराव नहीं होगा. इसका यह भी मतलब है कि repository_ctx.name, रिपो का कैननिकल नाम रिपोर्ट करता है. यह रिपो के नियम को कॉल करने के लिए तय किए गए नाम से अलग होता है.

मॉड्यूल एक्सटेंशन से जनरेट की गई रीपो को ध्यान में रखते हुए, रीपो दिखने से जुड़े कई नियम हैं:

  • Bazel मॉड्यूल रेपो, bazel_dep और use_repo के ज़रिए, अपनी MODULE.bazel फ़ाइल में शामिल किए गए सभी रेपो देख सकता है.
  • मॉड्यूल एक्सटेंशन से जनरेट की गई रिपो, एक्सटेंशन को होस्ट करने वाले मॉड्यूल को दिखने वाली सभी रिपो देख सकती है. इसके अलावा, वह उसी मॉड्यूल एक्सटेंशन से जनरेट की गई अन्य सभी रिपो भी देख सकती है. इन रिपो के नाम, रिपो के नियम के कॉल में बताए गए नामों के हिसाब से होते हैं.
    • इस वजह से, टकराव हो सकता है. अगर मॉड्यूल रिपो को foo नाम वाली कोई रिपो दिखती है और एक्सटेंशन foo नाम वाली कोई रिपो जनरेट करता है, तो उस एक्सटेंशन से जनरेट की गई सभी रिपो के लिए, foo का मतलब पहली रिपो से होगा.
  • इसी तरह, मॉड्यूल एक्सटेंशन के लागू करने वाले फ़ंक्शन में, एक्सटेंशन से बनाए गए रेपो, एट्रिब्यूट में अपने नाम से एक-दूसरे को रेफ़र कर सकते हैं. इससे कोई फ़र्क़ नहीं पड़ता कि उन्हें किस क्रम में बनाया गया है.
    • अगर मॉड्यूल को दिखने वाली किसी रिपॉज़िटरी से कोई टकराव होता है, तो रिपॉज़िटरी के नियम वाले एट्रिब्यूट को पास किए गए लेबल को Label कॉल में रैप किया जा सकता है. इससे यह पक्का किया जा सकता है कि वे उसी नाम के एक्सटेंशन से जनरेट की गई रिपॉज़िटरी के बजाय, मॉड्यूल को दिखने वाली रिपॉज़िटरी को रेफ़र करें.

मॉड्यूल एक्सटेंशन के रिपॉज़िटरी को बदलना और इंजेक्ट करना

रूट मॉड्यूल, मॉड्यूल एक्सटेंशन रीपो को बदलने या इंजेक्ट करने के लिए, override_repo और inject_repo का इस्तेमाल कर सकता है.

उदाहरण: rules_java के java_tools को वेंडर की कॉपी से बदलना

# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
  name = "my_java_tools",
  path = "vendor/java_tools",
)

bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")

override_repo(java_toolchains, remote_java_tools = "my_java_tools")

उदाहरण: सिस्टम के zlib के बजाय @zlib पर निर्भर रहने के लिए, Go डिपेंडेंसी को पैच करना

# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
  patches = [
    "//patches:my_module_zlib.patch",
  ],
  path = "example.com/my_module",
)
use_repo(go_deps, ...)

inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
 go_binary(
     name = "my_module",
     importpath = "example.com/my_module",
     srcs = ["my_module.go"],
-    copts = ["-lz"],
+    cdeps = ["@zlib"],
 )

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

इस सेक्शन में, एक्सटेंशन लिखने के सबसे सही तरीके बताए गए हैं, ताकि उन्हें आसानी से इस्तेमाल किया जा सके, बनाए रखा जा सके, और समय के साथ होने वाले बदलावों के हिसाब से बेहतर बनाया जा सके.

हर एक्सटेंशन को अलग फ़ाइल में रखें

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

नतीजे दोहराए जाने की संभावना के बारे में जानकारी देना और तथ्यों का इस्तेमाल करना

अगर आपका एक्सटेंशन, एक जैसे इनपुट (एक्सटेंशन टैग, पढ़ी जाने वाली फ़ाइलें वगैरह) के लिए हमेशा एक जैसी रिपॉज़िटरी तय करता है और खास तौर पर, ऐसे किसी डाउनलोड पर भरोसा नहीं करता है जिसे चेकसम से सुरक्षित नहीं किया गया है, तो reproducible = True के साथ extension_metadata को वापस लाने पर विचार करें. इससे Bazel, MODULE.bazel लॉकफ़ाइल में लिखते समय इस एक्सटेंशन को स्किप कर सकता है. इससे लॉकफ़ाइल का साइज़ कम रहता है और मर्ज करने से जुड़ी समस्याएं होने की संभावना कम हो जाती है. ध्यान दें कि Bazel, फिर से बनाए जा सकने वाले एक्सटेंशन के नतीजों को इस तरह से कैश मेमोरी में सेव करता है कि सर्वर रीस्टार्ट होने पर भी वे बने रहते हैं. इसलिए, लंबे समय तक चलने वाले एक्सटेंशन को भी परफ़ॉर्मेंस पर असर डाले बिना, फिर से बनाए जा सकने वाले एक्सटेंशन के तौर पर मार्क किया जा सकता है.

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

facts तब भी अमान्य नहीं होते, जब आपके मॉड्यूल एक्सटेंशन का कोड बदल जाता है. इसलिए, facts के स्ट्रक्चर में बदलाव होने पर, उसे मैनेज करने के लिए तैयार रहें. Bazel यह भी मानता है कि एक ही एक्सटेंशन के दो अलग-अलग आकलन से जनरेट हुई दो अलग-अलग facts डिक्शनरी को आसानी से मर्ज किया जा सकता है. इसका मतलब है कि दो डिक्शनरी पर | ऑपरेटर का इस्तेमाल करके उन्हें मर्ज किया जा सकता है. module_ctx.facts की कुछ पाबंदियों की वजह से, इस सुविधा को पूरी तरह से लागू नहीं किया जा सकता. जैसे, यह अपनी एंट्री की गिनती करने की सुविधा नहीं देता. इसमें सिर्फ़ कुंजी के हिसाब से लुकअप किया जा सकता है.

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

ऑपरेटिंग सिस्टम और आर्किटेक्चर पर निर्भरता के बारे में जानकारी देना

अगर आपका एक्सटेंशन, ऑपरेटिंग सिस्टम या उसके आर्किटेक्चर टाइप पर निर्भर करता है, तो os_dependent और arch_dependent बूलियन एट्रिब्यूट का इस्तेमाल करके, एक्सटेंशन की परिभाषा में इसकी जानकारी ज़रूर दें. इससे यह पक्का होता है कि अगर इनमें से किसी में भी बदलाव होता है, तो Bazel को दोबारा आकलन करने की ज़रूरत है.

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

सिर्फ़ रूट मॉड्यूल से, सीधे तौर पर रिपॉज़िटरी के नामों पर असर पड़ना चाहिए

ध्यान रखें कि जब कोई एक्सटेंशन रिपॉज़िटरी बनाता है, तो वे एक्सटेंशन के नेमस्पेस में बनाई जाती हैं. इसका मतलब है कि अगर अलग-अलग मॉड्यूल एक ही एक्सटेंशन का इस्तेमाल करते हैं और एक ही नाम वाली रिपॉज़िटरी बनाते हैं, तो टकराव हो सकता है. ऐसा अक्सर तब होता है, जब मॉड्यूल एक्सटेंशन के tag_class में ऐसा name आर्ग्युमेंट होता है जिसे रिपॉज़िटरी के नियम की name वैल्यू के तौर पर पास किया जाता है.

उदाहरण के लिए, मान लें कि रूट मॉड्यूल A, मॉड्यूल B पर निर्भर करता है. दोनों मॉड्यूल, mylang मॉड्यूल पर निर्भर हैं. अगर A और B, दोनों mylang.toolchain(name="foo") को कॉल करते हैं, तो दोनों mylang मॉड्यूल में foo नाम की एक रिपॉज़िटरी बनाने की कोशिश करेंगे. इससे गड़बड़ी होगी.

इससे बचने के लिए, सीधे तौर पर रिपॉज़िटरी का नाम सेट करने की सुविधा हटा दें या सिर्फ़ रूट मॉड्यूल को ऐसा करने की अनुमति दें. रूट मॉड्यूल को यह अनुमति देने में कोई समस्या नहीं है, क्योंकि कुछ भी इस पर निर्भर नहीं करेगा. इसलिए, इसे किसी दूसरे मॉड्यूल के नाम से टकराव होने की चिंता नहीं करनी होगी.