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

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

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

ज़्यादा जानकारी के लिए, Bazel और Starlark दस्तावेज़ देखें. साथ ही, नए नियमों के शुरुआती पॉइंट के तौर पर, नियम 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 Query, 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 चलाएं:

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

आगे जाना