Starlark היא שפה
דמוית Python שפותחה במקור לשימוש ב-Bazel ומאז היא אומצה
על ידי כלים אחרים. קובצי BUILD
ו-.bzl
של Bazel כתובים באופן דינמי בניב של Starstark בתור "שפת הבנייה", אך לעתים קרובות היא נקראת
"Starlark", במיוחד בעת הדגשה של התכונה מתבטאת בשפת Bazel מרחיב את שפת הליבה עם פונקציות רבות הקשורות ל-build
כמו glob
, genrule
, java_binary
וכן הלאה.
ניתן להיכנס אל בייז וגםסטארארק תיעוד למידע נוסף, וכןתבנית 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
מכיל שדות ושיטות שימושיים רבים; ניתן למצוא רשימה מקיפה
בהפניות 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
, Bazel מבצעת הערכה של כל הקבצים שהם טוענים. אם כמה קובציBUILD
נטענים foo.bzl, יוצג רק אירוע אחד של "bzl file review" מפני ש-Bazel שומרת את תוצאת ההערכה במטמון. - לא פונקציית הקריאה החוזרת
_foo_binary_impl
. השאילתה המקורית נטענת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
בכל פעם שמצהירים על קובץ, צריך לומר ל-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",
)
כדי לגשת לערך שבפונקציית הקריאה החוזרת, יש להשתמש בפונקציה 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"])
מתקדמים יותר
- מומלץ לעיין במסמכי העזר בנושא כללים.
- להכיר את השולחות.
- כדאי לעיין במאגר הדוגמאות שכולל דוגמאות נוספות של כללים.