البرنامج التعليمي للقواعد

Starlark هي لغة إعداد شبيهة بلغة Python ومُعدّة في الأصل للاستخدام في Bazel ومن ثمّ تم استخدامها من خلال أدوات أخرى. تتم كتابة الملفين BUILD و.bzl في Bazel بلهجة Starstark المعروفة بشكل صحيح باسم "لغة الإنشاء"، على الرغم من أنه يُشار إليها ببساطة باسم "Starlark"، وخاصة عند التركيز على يتم التعبير عن هذه الميزة في لغة التصميم بدلاً من كونها مضمّنة في تطبيق Bazel. تعزز البازل اللغة الأساسية باستخدام العديد من الوظائف المتعلقة بالإصدار، مثل glob وgenrule وjava_binary وغيرها.

ولمزيد من التفاصيل، يمكنك الاطّلاع على مستندات Bazel وStarlark للحصول على مزيد من التفاصيل ونموذج 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")

وتصميم:

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، يقيّم Baelel جميع الملفات التي يحمّلها. في حال تحميل ملفات BUILD متعدّدة، لن ترى سوى ورود واحد لـ "تقييم ملف bzl" لأن Google Barel تخزّن نتيجة التقييم مؤقتًا.
  • لا يتم استدعاء دالة رد الاتصال _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 query. عدم إعادة تقييم Bazel للرمز، لكنه يُعيد تشغيل أحداث الطباعة فقط. وبغض النظر عن حالة ذاكرة التخزين المؤقت، ستحصل على النتيجة نفسها.

إنشاء ملف

لجعل القاعدة أكثر فائدة، يُرجى تعديلها لإنشاء ملف. أولاً، يُرجى تحديد الملف وتحديد اسم له. في هذا المثال، أنشئ ملفًا بالاسم نفسه المستخدَم في الهدف:

ctx.actions.declare_file(ctx.label.name)

إذا شغّلت bazel build :all الآن، ستظهر لك رسالة خطأ:

The following files have no generating action:
bin2

عندما تُعلن عن ملف، عليك أن تخبر بازل بكيفية إنشائه من خلال إنشاء إجراء. يمكنك استخدام 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 بأن الملف هو نتيجة للقاعدة، وليس ملفًا مؤقتًا مستخدَمًا داخل تنفيذ القاعدة.

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، عن اعتمادية من الهدف الذي يملك السمة إلى الجمهور المستهدف الذي{ يظهر التصنيف 101} في قيمة السمة. ويشكّل هذا النوع من السمات أساس الرسم البياني المستهدف.

في الملف 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"])

المضي قدمًا