وحدات ماكرو

تتناول هذه الصفحة أساسيات استخدام وحدات الماكرو، وتشمل حالات استخدام نموذجية وتصحيح الأخطاء والاصطلاحات.

وحدة الماكرو هي دالة تُسمى من ملف BUILD يمكنها إنشاء قواعد. تُستخدم وحدات الماكرو بشكل أساسي لتغليف الرموز الحالية وإعادة استخدامها للرموز ووحدات أخرى أخرى. في نهاية مرحلة التحميل، لم تعد وحدات الماكرو موجودة، ولا يطّلع Bazel إلا على المجموعة الملموسة من القواعد التي تم إنشاء مثيل لها.

الاستخدام

تتمثل حالة الاستخدام النموذجية لوحدة الماكرو عندما تريد إعادة استخدام قاعدة.

على سبيل المثال، تُنشئ Gerule في ملف BUILD ملفًا باستخدام //:generator باستخدام وسيطة some_arg تم ترميزها نهائيًا في الأمر:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

إذا أردت إنشاء المزيد من الملفات باستخدام وسيطات مختلفة، يمكنك استخراج هذا الرمز إلى دالة ماكرو. لنطلب ماكرو file_generator الذي يضمّ معلَمتَي name وarg. استبدِل قاعدة السمات بما يلي:

load("//path:generator.bzl", "file_generator")

file_generator(
    name = "file",
    arg = "some_arg",
)

file_generator(
    name = "file-two",
    arg = "some_arg_two",
)

file_generator(
    name = "file-three",
    arg = "some_arg_three",
)

يمكنك هنا تحميل الرمز file_generator من ملف .bzl يقع في حزمة //path. بوضع تعريفات الدوال الكبيرة في ملف .bzl منفصل، تحافظ على ملفات BUILD نظيفة وبيانات تعريفية، ويمكن تحميل الملف .bzl من أي حزمة في مساحة العمل.

وأخيرًا، في نظام التشغيل path/generator.bzl، اكتب تعريف وحدة الماكرو لتلخيص تعريف المعلّمة الأصلية ومعلّمته:

def file_generator(name, arg, visibility=None):
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location //:generator) %s > $@" % arg,
    tools = ["//:generator"],
    visibility = visibility,
  )

يمكنك أيضًا استخدام وحدات الماكرو لسلسلة القواعد معًا. يعرض هذا المثال السلسلة ذات القواعد العامة، حيث تستخدم قاعدة التوزيع مخرجات قاعدة سابقة كإدخالات:

def chained_genrules(name, visibility=None):
  native.genrule(
    name = name + "-one",
    outs = [name + ".one"],
    cmd = "$(location :tool-one) $@",
    tools = [":tool-one"],
    visibility = ["//visibility:private"],
  )

  native.genrule(
    name = name + "-two",
    srcs = [name + ".one"],
    outs = [name + ".two"],
    cmd = "$(location :tool-two) $< $@",
    tools = [":tool-two"],
    visibility = visibility,
  )

يحدّد المثال قيمة مستوى رؤية للقاعدة الثانية فقط. ويسمح ذلك لمؤلفي وحدات الماكرو بإخفاء مخرجات القواعد المتوسطة من الاعتماد عليها من خلال أهداف أخرى في مساحة العمل.

توسيع وحدات الماكرو

إذا أردت التعرّف على وظيفة وحدة ماكرو، استخدِم الأمر query مع --output=build للاطّلاع على النموذج الموسّع:

$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
  name = "file",
  tools = ["//:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location //:generator) some_arg > $@",
)

إنشاء مثيل للقواعد المدمجة مع المحتوى

يمكن تنفيذ القواعد المدمجة مع المحتوى (القواعد التي لا تحتاج إلى بيان load()) من الوحدة الرئيسية.

def my_macro(name, visibility=None):
  native.cc_library(
    name = name,
    srcs = ["main.cc"],
    visibility = visibility,
  )

إذا كنت بحاجة إلى معرفة اسم الحزمة (على سبيل المثال، ملف BUILD الذي يستدعي وحدة الماكرو)، استخدِم الدالة original.package_name(). تجدر الإشارة إلى أنه لا يمكن استخدام native إلا في ملفات .bzl، وليس في ملفات WORKSPACE أو BUILD.

دقة التصنيف في وحدات الماكرو

بما أنه يتم تقييم وحدات الماكرو في مرحلة التحميل، يتم تفسير سلاسل التصنيفات، مثل "//foo:bar" التي تحدث في وحدة ماكرو، بالاستناد إلى ملف BUILD الذي يتم فيه استخدام وحدة الماكرو بدلاً من ملف .bzl الذي يتم تحديده. وبشكل عام، هذا السلوك غير مرغوب فيه لوحدات الماكرو التي يُقصد استخدامها في مستودعات أخرى، مثلاً لأنها جزء من مجموعة Starlark المنشورة.

للحصول على السلوك نفسه كما هو الحال مع قواعد Starlark، عليك وضع سلاسل التصنيفات باستخدام أداة إنشاء Label:

# @my_ruleset//rules:defs.bzl
def my_cc_wrapper(name, deps = [], **kwargs):
  native.cc_library(
    name = name,
    deps = deps + select({
      # Due to the use of Label, this label is resolved within @my_ruleset,
      # regardless of its site of use.
      Label("//config:needs_foo"): [
        # Due to the use of Label, this label will resolve to the correct target
        # even if the canonical name of @dep_of_my_ruleset should be different
        # in the main workspace, such as due to repo mappings.
        Label("@dep_of_my_ruleset//tools:foo"),
      ],
      "//conditions:default": [],
    }),
    **kwargs,
  )

تصحيح الأخطاء

  • سيوضّح لك bazel query --output=build //my/path:all الشكل الذي يظهر به ملف BUILD بعد التقييم. يتم توسيع جميع وحدات الماكرو والكرات الأرضية والحلقات. الحد المعروف: لا يتم عرض تعبيرات select حاليًا في الإخراج.

  • ويمكنك فلترة النتائج استنادًا إلى generator_function (الدالة التي أنشأت القواعد) أو generator_name (سمة الاسم لوحدة الماكرو): bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'

  • لمعرفة مكان إنشاء القاعدة foo بالضبط في ملف BUILD، يمكنك تجربة الخدعة التالية. أدرِج هذا السطر بالقرب من أعلى ملف BUILD: cc_library(name = "foo"). تشغيل Bazel وستحصل على استثناء عند إنشاء القاعدة foo (بسبب تعارض في الاسم)، الذي سيعرض لك تتبُّع تسلسل استدعاء الدوال البرمجية بالكامل.

  • يمكنك أيضًا استخدام الطباعة لتصحيح الأخطاء. وتعرض الرسالة كسطر سجلّ DEBUG أثناء مرحلة التحميل. باستثناء حالات نادرة، يمكنك إزالة استدعاءات print أو جعلها مشروطة ضمن معلَمة debugging التي يتم ضبطها تلقائيًا على False قبل إرسال الرمز إلى المستودع.

الأخطاء

إذا أردت حدوث خطأ، استخدِم الدالة fail. اشرح للمستخدم بوضوح الخطأ الذي حدث، وكيفية إصلاح ملف BUILD. لا يمكن اكتشاف خطأ.

def my_macro(name, deps, visibility=None):
  if len(deps) < 2:
    fail("Expected at least two values in deps")
  # ...

مؤتمرات

  • جميع الدوال العامة (الدوال التي لا تبدأ بشرطة سفلية) التي يجب أن تتضمّن وسيطة name. يجب ألا تكون هذه الوسيطة اختيارية (بدون't تقديم قيمة تلقائية).

  • يجب أن تستخدم الدوال العامة سلسلة نصية تتبع اصطلاحات Python.

  • في الملفات BUILD، يجب أن تكون وسيطة name لوحدات الماكرو وحدات وسيطة للكلمات الرئيسية (وليس وسيطة موضعية).

  • يجب أن تتضمن سمة name للقواعد التي يتم إنشاؤها بواسطة وحدة ماكرو وسيطة الاسم كبادئة. على سبيل المثال، تستطيع macro(name = "foo") إنشاء cc_library foo وقاعدة عامة foo_gen.

  • في معظم الحالات، يجب أن تكون المعلّمات الاختيارية ذات قيمة تلقائية None. يمكن تمرير None مباشرةً إلى القواعد الأصلية، حيث يتم التعامل معها بالطريقة نفسها كما لو لم يتم تمريرها في أي وسيطة. وبالتالي، لا حاجة إلى استبدالها بـ 0 أو False أو [] لهذا الغرض. وبدلاً من ذلك، يجب أن تؤكّد وحدة الماكرو القواعد التي تنشئها، لأن الإعدادات التلقائية قد تكون معقّدة أو قد تتغير بمرور الوقت. وبالإضافة إلى ذلك، تبدو المعلّمة التي يتم ضبطها صراحةً على قيمتها التلقائية مختلفة عن معلَمة لم يتم ضبطها مطلقًا (أو تم ضبطها على None) عند الوصول إليها من خلال لغة طلب البحث أو العناصر الداخلية في نظام الإصدار.

  • يجب أن تحتوي وحدات الماكرو على وسيطة visibility اختيارية.