פקודות מאקרו

דף זה עוסק בעקרונות השימוש בפקודות מאקרו, וכולל תרחישים נפוצים, ניפוי באגים ומוסכמות.

מאקרו הוא פונקציה שנקראת מתוך הקובץ BUILD שיכולה ליצור כללים. פקודות מאקרו משמשות בעיקר לאנקפסולות ולשימוש חוזר בקודים בכללים ובמאקרו אחרים. בסוף שלב הטעינה, פקודות המאקרו לא קיימות יותר, ו-Bazel רואה רק את קבוצת המוחשית של הכללים המוגדרים.

שימוש

תרחיש לדוגמה טיפוסי עבור מאקרו הוא כשרוצים לעשות שימוש חוזר בכלל.

לדוגמה, יצירה של ז'אנר בקובץ 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()) מהמודול native:

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

אם צריך לדעת את שם החבילה (לדוגמה, איזה קובץ BUILD קורא למאקרו), יש להשתמש בפונקציה native.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 לפני שליחת הקוד למאגר.

שגיאות

אם רוצים להשליך שגיאה, אפשר להשתמש בפונקציה נכשל. יש להסביר למשתמש בצורה ברורה מה השתבש ואיך לתקן את הקובץ BUILD שלו. לא ניתן לאתר שגיאה.

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

כנסים

  • כל הפונקציות הציבוריות (פונקציות שלא מתחילות בקו תחתון) שחייבות להתבצע באופן מיידי חייבות לכלול ארגומנט name. הארגומנט הזה לא יכול להיות אופציונלי (אין להגדיר ערך ברירת מחדל).

  • הפונקציות הציבוריות צריכות להשתמש ב-docstring בהתאם למוסכמות של Python.

  • בקובצי BUILD, הארגומנט name של המאקרו חייב להיות ארגומנט של מילת מפתח (לא ארגומנט של מיקום).

  • מאפיין name של הכללים שנוצרים על ידי מאקרו צריך לכלול את שם הארגומנט כקידומת. לדוגמה, macro(name = "foo") יכול ליצור את cc_library foo ואת הוריאציה שלו foo_gen.

  • ברוב המקרים, פרמטרים אופציונליים צריכים לקבל ערך ברירת מחדל של None. אפשר להעביר את None ישירות לכללים מותאמים, שמטפלים בהם כאילו לא עברתם לארגומנט כלשהו. לכן אין צורך להחליף אותו ב-0, ב-False או ב-[] למטרה זו. במקום זאת, רכיב המאקרו צריך להתאים לכללים שהוא יוצר, כי ברירות המחדל שלהם עשויות להיות מורכבות או משתנות לאורך זמן. בנוסף, פרמטר שמוגדר במפורש לערך ברירת המחדל שלו נראה שונה מפרמטר שמעולם לא הוגדר (או מוגדר ל-None), כשניגשים אליו דרך שפת השאילתה או תוכן פנימי של מערכת ה-build.

  • ברכיבי מאקרו צריך להיות ארגומנט visibility אופציונלי.