Starlark یک زبان پیکربندی شبیه پایتون است که در اصل برای استفاده در Bazel توسعه یافته و از آن زمان توسط ابزارهای دیگر استفاده شده است. فایلهای BUILD
و .bzl
به گویش Starlark نوشته شدهاند که به درستی به عنوان "Build Language" شناخته میشود، اگرچه اغلب به سادگی به عنوان "Starlark" نامیده میشود، به خصوص زمانی که تاکید میشود که یک ویژگی در زبان ساخت بیان میشود و بر خلاف وجود آن است. قسمت داخلی یا "بومی" بازل. 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
را فراخوانی می کنید، باید تابع callback تعریف کنید. منطق به آنجا می رود، اما می توانید فعلاً تابع را خالی بگذارید. آرگومان 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
فیلدها و متدهای مفید زیادی دارد. شما می توانید یک لیست جامع در مرجع API پیدا کنید.
کد را جویا شوید:
$ 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
، بازل تمام فایل هایی را که بارگذاری می کند ارزیابی می کند. اگر چندین فایلBUILD
در حال بارگیری foo.bzl هستند، شما فقط یک مورد "ارزیابی فایل bzl" را مشاهده خواهید کرد زیرا Bazel نتیجه ارزیابی را در حافظه پنهان ذخیره می کند. - تابع callback
_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 bazel query
ذخیره می شود. بازل کد را دوباره ارزیابی نمی کند، فقط رویدادهای چاپی را دوباره پخش می کند. صرف نظر از وضعیت کش، خروجی یکسانی را دریافت می کنید.
ایجاد یک فایل
برای مفیدتر کردن قانون خود، آن را برای ایجاد یک فایل به روز کنید. ابتدا فایل را اعلان کرده و نامی به آن بدهید. در این مثال، فایلی با همان نام هدف ایجاد کنید:
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",
)
برای دسترسی به مقدار در تابع callback، از 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"])
جلوتر رفتن
- به مستندات مرجع قوانین نگاهی بیندازید.
- با دپست ها آشنا شوید.
- مخزن نمونهها را بررسی کنید که شامل نمونههای دیگری از قوانین است.