آموزش قوانین

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

جلوتر رفتن