कस्टम क्रिया बनाने के लिए मैक्रो का उपयोग करना

समस्या की शिकायत करें सोर्स देखें Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Bazel के साथ रोज़ाना इंटरैक्शन, मुख्य रूप से कुछ कमांड के ज़रिए होता है: build, test, और run. हालांकि, कभी-कभी ये सुविधाएं सीमित लग सकती हैं. जैसे, आपको पैकेज को किसी रिपॉज़िटरी में पुश करना हो, उपयोगकर्ताओं के लिए दस्तावेज़ पब्लिश करना हो या Kubernetes के साथ कोई ऐप्लिकेशन डिप्लॉय करना हो. हालांकि, Bazel में publish या deploy कमांड नहीं है. ऐसे में, ये कार्रवाइयां कहां फ़िट होती हैं?

bazel run कमांड

Bazel, हर्मेटिसिटी, रीप्रोड्यूसिबिलिटी, और इंक्रीमेंटैलिटी पर फ़ोकस करता है. इसका मतलब है कि ऊपर दिए गए टास्क के लिए, build और test कमांड काम की नहीं हैं. ये कार्रवाइयां, सैंडबॉक्स में सीमित नेटवर्क ऐक्सेस के साथ चल सकती हैं. साथ ही, यह ज़रूरी नहीं है कि हर bazel build के साथ इन्हें फिर से चलाया जाए.

इसके बजाय, bazel run का इस्तेमाल करें. यह उन टास्क के लिए सबसे अच्छा है जिनके साइड इफ़ेक्ट ज़रूरी हैं. Bazel का इस्तेमाल करने वाले लोग, एक्ज़ीक्यूटेबल फ़ाइलें बनाने वाले नियमों के बारे में जानते हैं. साथ ही, नियम बनाने वाले लोग, "कस्टम वर्ब" के लिए, पैटर्न के एक सामान्य सेट का पालन कर सकते हैं.

जंगली में: rules_k8s

उदाहरण के लिए, rules_k8s, Bazel के लिए Kubernetes के नियमों पर विचार करें. मान लें कि आपका टारगेट यह है:

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

k8s_object नियम, staging टारगेट पर bazel build का इस्तेमाल करने पर, स्टैंडर्ड Kubernetes YAML फ़ाइल बनाता है. हालांकि, अतिरिक्त टारगेट भी k8s_object मैक्रो से बनाए जाते हैं. इनके नाम staging.apply और :staging.delete जैसे होते हैं. ये कार्रवाइयां करने के लिए, ये स्क्रिप्ट बनाते हैं. साथ ही, bazel run staging.apply के साथ इस्तेमाल करने पर, ये हमारी bazel k8s-apply या bazel k8s-delete कमांड की तरह काम करती हैं.

एक और उदाहरण: ts_api_guardian_test

इस पैटर्न को Angular प्रोजेक्ट में भी देखा जा सकता है. ts_api_guardian_test मैक्रो दो टारगेट बनाता है. पहला, स्टैंडर्ड nodejs_test टारगेट है. इसमें जनरेट किए गए कुछ आउटपुट की तुलना "गोल्डन" फ़ाइल से की जाती है. "गोल्डन" फ़ाइल का मतलब, ऐसी फ़ाइल से है जिसमें अनुमानित आउटपुट होता है. इसे सामान्य bazel test इनवोकेशन की मदद से बनाया और चलाया जा सकता है. angular-cli में, bazel test //etc/api:angular_devkit_core_api की मदद से ऐसा एक टारगेट चलाया जा सकता है.

समय के साथ, इस गोल्डन फ़ाइल को जायज़ वजहों से अपडेट करना पड़ सकता है. इसे मैन्युअल तरीके से अपडेट करना मुश्किल है और इसमें गड़बड़ियां हो सकती हैं. इसलिए, यह मैक्रो nodejs_binary टारगेट भी उपलब्ध कराता है. यह टारगेट, गोल्डन फ़ाइल से तुलना करने के बजाय उसे अपडेट करता है. असल में, एक ही टेस्ट स्क्रिप्ट को "verify" या "accept" मोड में चलाने के लिए लिखा जा सकता है. यह इस बात पर निर्भर करता है कि इसे कैसे शुरू किया गया है. यह उसी पैटर्न पर काम करता है जिसके बारे में आपने पहले सीखा है: इसमें कोई नेटिव bazel test-accept कमांड नहीं होती, लेकिन bazel run //etc/api:angular_devkit_core_api.accept का इस्तेमाल करके वही काम किया जा सकता है.

यह पैटर्न काफ़ी असरदार हो सकता है. साथ ही, इसे पहचानना सीखने के बाद यह काफ़ी सामान्य लगता है.

अपने नियमों को लागू करना

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

इसे समझने के लिए, एक काल्पनिक नियम बनाएं. यह नियम, Sphinx की मदद से एक वेबसाइट जनरेट करता है. इसमें एक मैक्रो भी शामिल है, ताकि एक अतिरिक्त टारगेट बनाया जा सके. इससे उपयोगकर्ता को वेबसाइट पब्लिश करने की सुविधा मिलती है. Sphinx की मदद से वेबसाइट जनरेट करने के लिए, यहां दिए गए मौजूदा नियम को देखें:

_sphinx_site = rule(
     implementation = _sphinx_impl,
     attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)

इसके बाद, इस तरह के नियम पर विचार करें. यह एक ऐसी स्क्रिप्ट बनाता है जिसे चलाने पर, जनरेट किए गए पेजों को पब्लिश किया जाता है:

_sphinx_publisher = rule(
    implementation = _publish_impl,
    attrs = {
        "site": attr.label(),
        "_publisher": attr.label(
            default = "//internal/sphinx:publisher",
            executable = True,
        ),
    },
    executable = True,
)

आखिर में, ऊपर दिए गए दोनों नियमों के लिए एक साथ टारगेट बनाने के लिए, यहां दिए गए सिंबॉलिक मैक्रो को तय करें. यह मैक्रो, Bazel 8 या इसके नए वर्शन में उपलब्ध है:

def _sphinx_site_impl(name, visibility, srcs, **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML. We
    # set `visibility = visibility` to make it visible to callers of the
    # macro.
    _sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above. We don't want it to be visible to callers of
    # our macro, so we omit visibility for it.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

sphinx_site = macro(
    implementation = _sphinx_site_impl,
    attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
    # Inherit common attributes like tags and testonly
    inherit_attrs = "common",
)

अगर आपको Bazel 8 से पहले के Bazel वर्शन के साथ काम करने वाले एक्सटेंशन बनाने हैं, तो आपको इसके बजाय लेगसी मैक्रो को इस तरह से तय करना होगा:

def sphinx_site(name, srcs = [], **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML.
    _sphinx_site(name = name, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

BUILD फ़ाइलों में, मैक्रो का इस्तेमाल इस तरह करें कि वह सिर्फ़ प्राइमरी टारगेट बनाता हो:

sphinx_site(
    name = "docs",
    srcs = ["index.md", "providers.md"],
)

इस उदाहरण में, "docs" टारगेट बनाया गया है. यह ठीक उसी तरह बनाया गया है जैसे मैक्रो एक स्टैंडर्ड, सिंगल Bazel नियम हो. बनाए जाने पर, यह नियम कुछ कॉन्फ़िगरेशन जनरेट करता है. साथ ही, Sphinx को चलाता है, ताकि एचटीएमएल साइट बनाई जा सके. यह साइट, मैन्युअल तरीके से जांच करने के लिए तैयार होती है. हालांकि, एक अतिरिक्त "docs.publish" टारगेट भी बनाया जाता है. यह साइट को पब्लिश करने के लिए स्क्रिप्ट बनाता है. प्राइमरी टारगेट का आउटपुट देखने के बाद, इसे सार्वजनिक तौर पर पब्लिश करने के लिए bazel run :docs.publish का इस्तेमाल किया जा सकता है. यह ठीक उसी तरह काम करता है जैसे काल्पनिक bazel publish कमांड.

_sphinx_publisherनियम को लागू करने पर, यह कैसा दिखेगा, इसके बारे में तुरंत पता नहीं चलता. अक्सर, इस तरह की कार्रवाइयां लॉन्चर शेल स्क्रिप्ट लिखती हैं. इस तरीके में आम तौर पर, ctx.actions.expand_template का इस्तेमाल करके बहुत ही आसान शेल स्क्रिप्ट लिखी जाती है. इस मामले में, पब्लिशर बाइनरी को मुख्य टारगेट के आउटपुट के पाथ के साथ शुरू किया जाता है. इस तरह, पब्लिशर का लागू किया गया कोड सामान्य बना रहता है. साथ ही, _sphinx_site नियम सिर्फ़ एचटीएमएल जनरेट कर सकता है. इन दोनों को एक साथ इस्तेमाल करने के लिए, यह छोटी स्क्रिप्ट ज़रूरी है.

rules_k8s में, .apply यही काम करता है: expand_template apply.sh.tpl के आधार पर, एक बहुत ही आसान बैश स्क्रिप्ट लिखता है. यह स्क्रिप्ट, प्राइमरी टारगेट के आउटपुट के साथ kubectl को चलाती है. इसके बाद, इस स्क्रिप्ट को bazel run :staging.apply की मदद से बनाया और चलाया जा सकता है. इससे k8s_object टारगेट के लिए, k8s-apply कमांड मिलती है.