Starlark, Python की तरह का कॉन्फ़िगरेशन भाषा है. इसे मूल रूप से Bazel में इस्तेमाल करने के लिए बनाया गया था. इसे बाद में दूसरे टूल ने अपनाया. बैजल की BUILD
और .bzl
फ़ाइलें, स्टारलार्क की एक बोली में लिखी गई हैं. इस बोली को "बिल्ड लैंग्वेज" के नाम से भी जाना जाता है. हालांकि, इसे अक्सर "स्टारलार्क" कहा जाता है. खास तौर पर, जब इस बात पर ज़ोर दिया जाता है कि कोई सुविधा, बैजल की बिल्ट-इन या "नेटिव" हिस्से के बजाय बिल्ड लैंग्वेज में बताई गई है. Bazel, ऐप्लिकेशन बनाने से जुड़े कई फ़ंक्शन की मदद से मुख्य भाषा को बेहतर बनाता है. जैसे, glob
, genrule
, java_binary
वगैरह.
ज़्यादा जानकारी के लिए, Bazel और 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
फ़ाइल की जांच करने से पहले, Bazel उन सभी फ़ाइलों की जांच करता है जिन्हें वह लोड करता है. अगर एक से ज़्यादाBUILD
फ़ाइलें foo.bzl लोड हो रही हैं, तो आपको "bzl फ़ाइल की जांच" का सिर्फ़ एक बार दिखेगा. ऐसा इसलिए, क्योंकि Bazel जांच के नतीजे को कैश मेमोरी में सेव करता है. - कॉलबैक फ़ंक्शन
_foo_binary_impl
को कॉल नहीं किया जाता है. Bazel क्वेरी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
का इवैलुएशन कैश मेमोरी में सेव किया जाता है.
Bazel सिर्फ़ तब print
स्टेटमेंट देता है, जब वे असल में लागू किए जाते हैं.
फ़ाइल बनाना
अपने नियम को ज़्यादा उपयोगी बनाने के लिए, उसे अपडेट करके फ़ाइल जनरेट करें. सबसे पहले, फ़ाइल के बारे में बताएं और उसे एक नाम दें. इस उदाहरण में, टारगेट के नाम से ही एक फ़ाइल बनाएं:
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 तब तक फ़ाइल नहीं बनाएगा, जब तक कि वाकई में उसका अनुरोध न किया जाए. इसलिए, आखिरी बार 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
का दस्तावेज़ देखें.
आप दूसरे तरह के एट्रिब्यूट का भी इस्तेमाल कर सकते हैं, जैसे कि 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"])
आगे बढ़ें
- नियमों के बारे में जानकारी देने वाले दस्तावेज़ देखें.
- depsets के बारे में जानें.
- डेटा स्टोर करने की जगह के उदाहरण देखें, जिसमें नियमों के और उदाहरण शामिल हैं.