नियमों का ट्यूटोरियल

Starlark, Python जैसी कॉन्फ़िगरेशन भाषा है. इसे मूल रूप से बेज़ेल में इस्तेमाल करने के लिए बनाया गया है. इसे दूसरे टूल ने अपनाया है. बेज़ेल की BUILD और .bzl फ़ाइलें "Starlark" की भाषा में लिखी गई हैं, जिसे ठीक से "बिल्ड भाषा" के नाम से जाना जाता है. हालांकि, इसे अक्सर "Starlark" के नाम से जाना जाता है, खास तौर पर जब किसी उदाहरण पर ज़ोर दिया जाता है यह सुविधा बिल्ड में पहले से मौजूद है या मूल भाषा के बजाय बिल्ड की भाषा में दिखाई गई है. बेज़ेल कई बिल्ड-आधारित फ़ंक्शन जैसे glob, genrule, java_binary वगैरह के साथ मुख्य भाषा को बेहतर बनाती है.

ज़्यादा जानकारी के लिए, बेज़ेल और Starlark के दस्तावेज़ देखें. साथ ही, नियम नियम टेंप्लेट के तौर पर देखें नए नियमों का शुरुआती बिंदु.

खाली नियम

अपना पहला नियम बनाने के लिए, फ़ाइल foo.bzl बनाएं:

def _foo_binary_impl(ctx):
    pass

foo_binary = rule(
    implementation = _foo_binary_impl,
)

जब आप rule फ़ंक्शन को कॉल करते हैं, तो आपको कॉलबैक फ़ंक्शन तय करना होगा. ऐसा करने से, तर्क यहां काम करने लगेगा. हालांकि, आप अभी फ़ंक्शन को खाली छोड़ सकते हैं. ctx आर्ग्युमेंट, टारगेट के बारे में जानकारी देता है.

आप नियम लोड करके, BUILD फ़ाइल से इसका इस्तेमाल कर सकते हैं.

एक ही डायरेक्ट्री में BUILD फ़ाइल बनाएं:

load(":foo.bzl", "foo_binary")

foo_binary(name = "bin")

अब टारगेट बनाया जा सकता है:

$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)

हालांकि, यह नियम कुछ नहीं करता है, लेकिन यह पहले से ही दूसरे नियमों की तरह काम करता है: इसमें ज़रूरी नाम है, लेकिन यह visibility, testonly, और tags जैसे सामान्य एट्रिब्यूट के साथ काम करता है.

इवैलुएशन मॉडल

आगे बढ़ने से पहले, यह समझना ज़रूरी है कि कोड का मूल्यांकन कैसे किया गया है.

foo.bzl को कुछ प्रिंट स्टेटमेंट के साथ अपडेट करें:

def _foo_binary_impl(ctx):
    print("analyzing", ctx.label)

foo_binary = rule(
    implementation = _foo_binary_impl,
)

print("bzl file evaluation")

और बिल्ड:

load(":foo.bzl", "foo_binary")

print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")

ctx.label विश्लेषण किए जा रहे टारगेट के लेबल से मेल खाता है. ctx ऑब्जेक्ट में कई उपयोगी फ़ील्ड और तरीके हैं आप एपीआई के रेफ़रंस में पूरी सूची देख सकते हैं.

कोड पर क्वेरी करें:

$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1

इन बातों पर ध्यान दें:

  • पहले "bzl फ़ाइल इवैलुएशन" प्रिंट की जाती है. BUILD की फ़ाइल का मूल्यांकन करने से पहले, Bazell की ओर से लोड की जाने वाली सभी फ़ाइलों का मूल्यांकन किया जाता है. अगर एक से ज़्यादा BUILD फ़ाइलें foo.bzl लोड कर रही हैं, तो आपको "bzl फ़ाइल इवैलुएशन" का सिर्फ़ एक बार जवाब दिखेगा. ऐसा इसलिए है, क्योंकि बेजल, जांच के नतीजे को कैश मेमोरी में सेव करती है.
  • कॉलबैक फ़ंक्शन _foo_binary_impl को कॉल नहीं किया जाता. बेज़ल क्वेरी BUILD फ़ाइलें लोड करती है, लेकिन टारगेट का विश्लेषण नहीं करती.

टारगेट का विश्लेषण करने के लिए, cquery ("कॉन्फ़िगर की गई क्वेरी") या build निर्देश का इस्तेमाल करें:

$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...

जैसा कि आप देख सकते हैं, अब _foo_binary_impl को हर टारगेट के लिए एक बार और दो बार कॉल किया जाता है.

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

फ़ाइल बनाना

अपने नियम को ज़्यादा उपयोगी बनाने के लिए, उसे अपडेट करके फ़ाइल जनरेट करें. सबसे पहले, फ़ाइल की जानकारी दें और उसे कोई नाम दें. इस उदाहरण में, टारगेट के नाम वाली फ़ाइल बनाएं:

ctx.actions.declare_file(ctx.label.name)

अगर आप अभी bazel build :all चलाते हैं, तो आपको एक गड़बड़ी मिलेगी:

The following files have no generating action:
bin2

जब भी आप किसी फ़ाइल का एलान करते हैं, तो आपको कोई कार्रवाई करके, बेज़ल को उसे जनरेट करने के तरीके के बारे में बताना होगा. दिए गए कॉन्टेंट वाली फ़ाइल बनाने के लिए, ctx.actions.write का इस्तेमाल करें.

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello\n",
    )

कोड मान्य है, लेकिन यह कुछ नहीं करेगा:

$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)

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

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello!\n",
    )
    return [DefaultInfo(files = depset([out]))]

DefaultInfo और depset के फ़ंक्शन बाद में देखें. अभी के लिए, मान लीजिए कि आखिरी लाइन किसी नियम के आउटपुट चुनने का तरीका है.

अब, बेज़ल चलाएं:

$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
  bazel-bin/bin1

$ cat bazel-bin/bin1
Hello!

आपने एक फ़ाइल जनरेट कर ली है!

विशेषताएं

नियम को ज़्यादा उपयोगी बनाने के लिए, attr मॉड्यूल का इस्तेमाल करके नए एट्रिब्यूट जोड़ें और नियम की परिभाषा अपडेट करें.

username नाम का स्ट्रिंग एट्रिब्यूट जोड़ें:

foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "username": attr.string(),
    },
)

इसके बाद, इसे BUILD फ़ाइल में सेट करें:

foo_binary(
    name = "bin",
    username = "Alice",
)

कॉलबैक फ़ंक्शन में मान ऐक्सेस करने के लिए, ctx.attr.username का इस्तेमाल करें. उदाहरण के लिए:

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello {}!\n".format(ctx.attr.username),
    )
    return [DefaultInfo(files = depset([out]))]

ध्यान दें कि आप एट्रिब्यूट को ज़रूरी बना सकते हैं या डिफ़ॉल्ट मान सेट कर सकते हैं. attr.string के दस्तावेज़ देखें. आप दूसरी तरह के एट्रिब्यूट भी इस्तेमाल कर सकते हैं, जैसे कि बूलियन या इंटेजर की सूची.

डिपेंडेंसी

निर्भरता से जुड़े एट्रिब्यूट, जैसे किattr.label औरattr.label_list , एट्रिब्यूट के मालिकाना हक वाले टारगेट से उस डिपेंडेंसी का एलान करें जिसका एट्रिब्यूट, एट्रिब्यूट की वैल्यू में दिखता है. इस तरह का एट्रिब्यूट, टारगेट ग्राफ़ के आधार को बनाता है.

BUILD फ़ाइल में, टारगेट लेबल, स्ट्रिंग ऑब्जेक्ट के तौर पर दिखता है, जैसे कि //pkg:name. लागू करने के फ़ंक्शन में, टारगेट को Target ऑब्जेक्ट के तौर पर ऐक्सेस किया जा सकेगा. उदाहरण के लिए, Target.files का इस्तेमाल करके टारगेट से मिली फ़ाइलें देखें.

एक से ज़्यादा फ़ाइलें

डिफ़ॉल्ट रूप से, सिर्फ़ नियमों के ज़रिए बनाए गए टारगेट डिपेंडेंसी के तौर पर दिख सकते हैं (जैसे कि foo_library() टारगेट). अगर आप चाहते हैं कि एट्रिब्यूट, टारगेट फ़ाइलों को स्वीकार करे (जैसे, रिपॉज़िटरी में सोर्स फ़ाइलें), तो आप इसके साथ ऐसा कर सकते हैं allow_files और स्वीकार किए गए फ़ाइल एक्सटेंशन की सूची तय करें (याTrue किसी भी फ़ाइल एक्सटेंशन को अनुमति देने के लिए):

"srcs": attr.label_list(allow_files = [".java"]),

फ़ाइलों की सूची को ctx.files.<attribute name> से ऐक्सेस किया जा सकता है. उदाहरण के लिए, srcs एट्रिब्यूट में मौजूद फ़ाइलों की सूची से ऐक्सेस किया जा सकता है

ctx.files.srcs

एक फ़ाइल

अगर आपको सिर्फ़ एक फ़ाइल की ज़रूरत है, तो allow_single_file का इस्तेमाल करें:

"src": attr.label(allow_single_file = [".java"])

इसके बाद, इस फ़ाइल को ctx.file.<attribute name> में ऐक्सेस किया जा सकता है:

ctx.file.src

टेंप्लेट की मदद से फ़ाइल बनाएं

आप ऐसा नियम बना सकते हैं जो टेंप्लेट के आधार पर .cc फ़ाइल जनरेट करता है. साथ ही, आप नियम लागू करने वाले फ़ंक्शन में बनाई गई स्ट्रिंग का आउटपुट पाने के लिए ctx.actions.write का इस्तेमाल कर सकते हैं, लेकिन इसमें दो समस्याएं हैं. सबसे पहले, जैसे-जैसे टेंप्लेट बड़ा होता जाता है, वैसे-वैसे इसे एक अलग फ़ाइल में रखना और विश्लेषण के चरण के दौरान बड़ी स्ट्रिंग बनाना बंद हो जाता है. दूसरा, उपयोगकर्ता के लिए अलग फ़ाइल का इस्तेमाल करना ज़्यादा आसान है. इसके बजाय, ctx.actions.expand_template का इस्तेमाल करें, जो टेम्प्लेट फ़ाइल के बदले में काम करता है.

टेंप्लेट फ़ाइल पर डिपेंडेंसी का एलान करने के लिए, template एट्रिब्यूट बनाएं:

def _hello_world_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + ".cc")
    ctx.actions.expand_template(
        output = out,
        template = ctx.file.template,
        substitutions = {"{NAME}": ctx.attr.username},
    )
    return [DefaultInfo(files = depset([out]))]

hello_world = rule(
    implementation = _hello_world_impl,
    attrs = {
        "username": attr.string(default = "unknown person"),
        "template": attr.label(
            allow_single_file = [".cc.tpl"],
            mandatory = True,
        ),
    },
)

उपयोगकर्ता इस नियम का इस्तेमाल इस तरह कर सकते हैं:

hello_world(
    name = "hello",
    username = "Alice",
    template = "file.cc.tpl",
)

cc_binary(
    name = "hello_bin",
    srcs = [":hello"],
)

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

    "_template": attr.label(
        allow_single_file = True,
        default = "file.cc.tpl",
    ),

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

exports_files(["file.cc.tpl"])

आगे जाना