استفاده از ماکروها برای ایجاد افعال سفارشی

تعامل روزانه با Bazel در درجه اول از طریق چند دستور انجام می شود: build ، test و run . با این حال، گاهی اوقات، این موارد ممکن است محدود به نظر برسد: ممکن است بخواهید بسته ها را به یک مخزن فشار دهید، اسناد را برای کاربران نهایی منتشر کنید، یا یک برنامه کاربردی را با Kubernetes مستقر کنید. اما بازل دستور publish یا deploy ندارد - این اقدامات در کجا قرار می گیرند؟

دستور اجرای bazel

تمرکز Bazel بر هرمتیک، تکرارپذیری و افزایش به این معنی است که دستورات build و test برای کارهای فوق مفید نیستند. این کنش‌ها ممکن است در جعبه ایمنی با دسترسی محدود به شبکه اجرا شوند و اجرای مجدد آنها با هر bazel build تضمین نمی‌شود.

درعوض، برای کارهایی که می‌خواهید عوارض جانبی داشته باشند، به bazel run تکیه کنید. کاربران Bazel به قوانینی که فایل‌های اجرایی ایجاد می‌کنند عادت کرده‌اند، و نویسندگان قوانین می‌توانند از مجموعه‌ای از الگوهای رایج برای تعمیم آن به «افعال سفارشی» پیروی کنند.

در طبیعت: rules_k8s

برای مثال rules_k8s را در نظر بگیرید، قوانین Kubernetes برای Bazel. فرض کنید هدف زیر را دارید:

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

قانون k8s_object یک فایل استاندارد Kubernetes YAML را زمانی که از bazel build bazel در هدف staging استفاده می‌شود، می‌سازد. با این حال، اهداف اضافی نیز توسط ماکرو k8s_object با نام‌هایی مانند staging.apply و :staging.delete . این اسکریپت‌ها را برای انجام آن اقدامات می‌سازند، و وقتی با bazel run staging.apply ، مانند bazel k8s-apply یا bazel k8s-delete .

مثال دیگر: ts_api_guardian_test

این الگو در پروژه Angular نیز قابل مشاهده است. ماکرو ts_api_guardian_test دو هدف تولید می کند. اولین مورد یک هدف استاندارد nodejs_test است که برخی از خروجی های تولید شده را با یک فایل طلایی (یعنی فایلی حاوی خروجی مورد انتظار) مقایسه می کند. این را می توان با فراخوانی bazel test معمولی ساخته و اجرا کرد. در angular-cli ، می‌توانید یکی از این اهداف را با bazel test //etc/api:angular_devkit_core_api کنید.

با گذشت زمان، این فایل طلایی ممکن است به دلایل قانونی نیاز به به روز رسانی داشته باشد. به روز رسانی این به صورت دستی خسته کننده و مستعد خطا است، بنابراین این ماکرو همچنین یک هدف nodejs_binary را ارائه می دهد که به جای مقایسه با آن، فایل طلایی را به روز می کند. به طور مؤثر، همان اسکریپت آزمایشی را می توان برای اجرا در حالت "تأیید" یا "پذیرش"، بر اساس نحوه فراخوانی آن نوشت. این از همان الگویی پیروی می کند که قبلاً یاد گرفته اید: هیچ دستور بومی bazel test-accept وجود ندارد، اما همان اثر را می توان با bazel run //etc/api:angular_devkit_core_api.accept به دست آورد.

این الگو می تواند بسیار قدرتمند باشد، و زمانی که یاد بگیرید آن را تشخیص دهید کاملاً رایج است.

انطباق قوانین خود

ماکروها قلب این الگو هستند. ماکروها مانند قوانین استفاده می شوند، اما می توانند چندین هدف ایجاد کنند. به طور معمول، آنها یک هدف با نام مشخص شده ایجاد می کنند که عملیات ساخت اولیه را انجام می دهد: شاید یک باینری معمولی، یک تصویر Docker یا یک آرشیو از کد منبع بسازد. در این الگو، اهداف اضافی برای تولید اسکریپت‌هایی ایجاد می‌شوند که اثرات جانبی را بر اساس خروجی هدف اولیه انجام می‌دهند، مانند انتشار باینری حاصل یا به‌روزرسانی خروجی آزمایشی مورد انتظار.

برای نشان دادن این موضوع، یک قانون خیالی که یک وب سایت با Sphinx ایجاد می کند را با یک ماکرو بپیچید تا یک هدف اضافی ایجاد کنید که به کاربر اجازه می دهد در صورت آماده شدن آن را منتشر کند. قانون موجود زیر را برای ایجاد یک وب سایت با Sphinx در نظر بگیرید:

_sphinx_site = rule(
     implementation = _sphinx_impl,
     attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)

در مرحله بعد، قاعده ای مانند زیر را در نظر بگیرید، که اسکریپتی می سازد که هنگام اجرا، صفحات تولید شده را منتشر می کند:

_sphinx_publisher = rule(
    implementation = _publish_impl,
    attrs = {
        "site": attr.label(),
        "_publisher": attr.label(
            default = "//internal/sphinx:publisher",
            executable = True,
        ),
    },
    executable = True,
)

در نهایت، ماکرو زیر را برای ایجاد اهداف برای هر دو قانون فوق با هم تعریف کنید:

def sphinx_site(name, srcs = [], **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML.
    _sphinx_site(name = name, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

در فایل های BUILD ، از ماکرو استفاده کنید که انگار فقط هدف اصلی را ایجاد می کند:

sphinx_site(
    name = "docs",
    srcs = ["index.md", "providers.md"],
)

در این مثال، یک هدف "docs" ایجاد می شود، درست مثل اینکه ماکرو یک قانون استاندارد و واحد Bazel است. زمانی که این قانون ساخته شد، برخی از تنظیمات را ایجاد می‌کند و Sphinx را برای تولید یک سایت HTML، آماده برای بازرسی دستی، اجرا می‌کند. با این حال، یک هدف اضافی "docs.publish" نیز ایجاد می شود که یک اسکریپت برای انتشار سایت ایجاد می کند. هنگامی که خروجی هدف اولیه را بررسی کردید، می‌توانید از bazel run :docs.publish برای انتشار آن برای مصرف عمومی استفاده کنید، درست مانند یک فرمان bazel publish .

بلافاصله مشخص نیست که اجرای قانون _sphinx_publisher ممکن است چگونه باشد. اغلب، اقداماتی مانند این یک اسکریپت پوسته لانچر را می نویسند. این روش معمولاً شامل استفاده از ctx.actions.expand_template برای نوشتن یک اسکریپت پوسته بسیار ساده است، در این مورد با فراخوانی باینری ناشر با یک مسیر به خروجی هدف اولیه. به این ترتیب، اجرای ناشر می تواند عمومی بماند، قانون _sphinx_site فقط می تواند HTML تولید کند، و این اسکریپت کوچک تمام چیزی است که برای ترکیب این دو با هم لازم است.

در rules_k8s ، این در واقع همان کاری است که .apply انجام می دهد: expand_template یک اسکریپت Bash بسیار ساده می نویسد، بر اساس apply.sh.tpl ، که kubectl را با خروجی هدف اصلی اجرا می کند. سپس این اسکریپت را می توان با bazel run :staging.apply ساخت و اجرا کرد و به طور موثر دستور k8s-apply را برای اهداف k8s_object می کند.