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

संग्रह की मदद से व्यवस्थित रहें अपनी प्राथमिकताओं के आधार पर, कॉन्टेंट को सेव करें और कैटगरी में बांटें.
समस्या की शिकायत करें स्रोत देखें

Starlark, Python की तरह कॉन्फ़िगरेशन करने की भाषा है. इसे मूल रूप से Bazel में इस्तेमाल करने के लिए बनाया गया था और बाद में, इसे दूसरे टूल के साथ इस्तेमाल किया गया. Bazel की BUILD और .bzl फ़ाइलें, Starlark की बोली में लिखी जाती हैं. इस भाषा को "Build Language" के नाम से जाना जाता है. हालांकि, इसे अक्सर "Starlark" कहा जाता है. खास तौर पर, जब इस बात पर ज़ोर दिया जाता है कि यह सुविधा बिल्ड की भाषा में बनाई गई है, न कि "Ball" की पहले से मौजूद या "नेटिव" हिस्से में. Bazel ने कई बिल्ड-फ़ंक्शन (जैसे, 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 फ़ाइल का मूल्यांकन करने से पहले, Bazel सभी लोड की गई फ़ाइलों का मूल्यांकन करता है. अगर BUILD की एक से ज़्यादा फ़ाइलें foo.bzl के लिए लोड हो रही हैं, तो आपको "bzl फ़ाइल की जांच" का सिर्फ़ एक इंस्टेंस दिखेगा, क्योंकि Bazel ने आकलन के नतीजे को कैश मेमोरी में सेव किया था.
  • कॉलबैक फ़ंक्शन _foo_binary_impl को कॉल नहीं किया जाता. Bazel क्वेरी से 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 फ़ाइल इवैलुएशन" फिर से प्रिंट किया गया है. हालांकि, foo.bzl का आकलन bazel query पर कॉल करने के बाद, कैश मेमोरी में सेव कर लिया जाता है. Bazel कोड का फिर से मूल्यांकन नहीं करता है, वह सिर्फ़ प्रिंट इवेंट को फिर से चलाता है. कैश मेमोरी की स्थिति चाहे जो भी हो, लेकिन आउटपुट एक जैसा ही होता है.

फ़ाइल बना रहा है

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

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

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

The following files have no generating action:
bin2

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

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"])

आगे बढ़ें