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