नियम ट्यूटोरियल

समस्या की शिकायत करें सोर्स देखें ठीक

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

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

खाली नियम

अपना पहला नियम बनाने के लिए, 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 फ़ाइल का आकलन करने से पहले, Baze उन सभी फ़ाइलों का आकलन करता है जो यह लोड होती हैं. अगर कई BUILD फ़ाइलें foo.bzl लोड हो रही हैं, तो आपको "bzl फ़ाइल इवैलुएशन" सिर्फ़ एक बार दिखेगी. इसकी वजह यह है कि बैज, इवैलुएशन के नतीजे को कैश मेमोरी में सेव करता है.
  • कॉलबैक फ़ंक्शन _foo_binary_impl को कॉल नहीं किया गया है. Basel क्वेरी, BUILD फ़ाइलें लोड करती है, लेकिन टारगेट का विश्लेषण नहीं करती.

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

$ bazel build :all
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 फ़ाइल इवैलुएशन" और न ही "BUILD फ़ाइल" को फिर से प्रिंट किया गया है, क्योंकि bazel query को कॉल करने के बाद, foo.bzl का इवैलुएशन कैश मेमोरी में सेव किया जाता है. Basel ने print स्टेटमेंट सिर्फ़ तब बनाए, जब उन्हें असल में एक्ज़ीक्यूट किया गया.

फ़ाइल बनाना

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

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

अभी bazel build :all का इस्तेमाल करने पर, आपको गड़बड़ी का मैसेज दिखेगा:

The following files have no generating action:
bin2

जब भी आप किसी फ़ाइल का एलान करते हैं, तो आपको Basel को बताना होगा कि कोई कार्रवाई बनाकर उसे कैसे जनरेट करना है. दिए गए कॉन्टेंट के साथ फ़ाइल बनाने के लिए, 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 फ़ंक्शन ने एक कार्रवाई रजिस्टर की, जिससे Baज़ल को फ़ाइल जनरेट करने का तरीका पता चला. लेकिन Basel फ़ाइल को तब तक नहीं बनाएगा, जब तक कि असल में उसका अनुरोध नहीं किया जाता. इसलिए, आपको बस बेज़ल को बताना होगा कि यह फ़ाइल नियम का आउटपुट है, न कि नियम लागू करने के दौरान इस्तेमाल की जाने वाली अस्थायी फ़ाइल.

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 फ़ंक्शन बाद में देखें. फ़िलहाल, यह मान लें कि आखिरी लाइन ही किसी नियम के आउटपुट को चुनने का तरीका है.

अब, Bagel चलाएं:

$ 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 का दस्तावेज़ देखें. आप boolean या पूर्णांकों की सूची जैसे दूसरे तरह के एट्रिब्यूट का भी इस्तेमाल कर सकते हैं.

डिपेंडेंसी

डिपेंडेंसी एट्रिब्यूट, जैसे कि 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"])

आगे बढ़ते रहें