דף זה מסביר את העקרונות הבסיסיים והיתרונות של השימוש בהיבטים ומספק דוגמאות מתקדמות ומתקדמיות.
ההיבטים מאפשרים הרחבה של תרשימי תלות ב-build עם מידע נוסף ופעולות. כמה תרחישים אופייניים שבהם היבטים יכולים להועיל:
- ספקי צד שלישי שמשלבים את Bazel יכולים להשתמש בהיבטים כדי לאסוף מידע על הפרויקט.
- כלים ליצירת קודים יכולים למנף את ההיבטים לביצוע לפי הקלט
של היעד . לדוגמה, קובצי
BUILD
יכולים לציין היררכיה של הגדרות ספריית protobuf וכללים ספציפיים לשפה יכולים להשתמש בהיבטים כדי לצרף פעולות ליצירת קוד תמיכה בפרוטוקול.
מידע בסיסי
BUILD
הקבצים מספקים תיאור של קוד המקור של הפרויקט: אילו
קובצי מקור הם חלק מהפרויקט, אילו אובייקטים (יעדים) צריך לבנות מתוך הקבצים האלה, מה התלויות בקבצים האלה וכו'. באזל משתמש במידע הזה כדי ליצור מבנה, כלומר, הוא יוצר את קבוצת הפעולות שצריך כדי ליצור את פריטי התוכן (למשל, מהדר או מריץ) חברת Bazel מבצעת את הפעולות האלה על ידי יצירת תרשים תלות בין יעדים וביקור בתרשים הזה כדי לאסוף את הפעולות האלה.
יש לשקול את הקובץ BUILD
הבא:
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
קובץ ה-BUILD
הזה מגדיר תרשים תלות שמוצג באיור הבא:
איור 1. תרשים תלות של קובץ אחד (BUILD
).
ב-Bazel מנתחים את תרשים התלות הזה על ידי קריאה לפונקציית הטמעה של
הכלל התואם (במקרה הזה "JavaScript_library" ) עבור כל
יעד בדוגמה שלמעלה. פונקציות של הטמעת כללים יוצרות פעולות
שמייצרות חפצים, כמו קבצים של .jar
, ומעבירות מידע, כמו מיקומים
ושמות של פריטי המידע האלה, אל יחסי התלות של היעדים האלה
בספקים.
ההיבטים דומים לכללים מכיוון שהם כוללים פונקציית הטמעה שיוצרת פעולות והחזרות. עם זאת, העוצמה שלהם מגיעה מהאופן שבו תרשים התלות נוצר עבורם. להיבט יש הטמעה ורשימה של כל המאפיינים שהוא מפיץ. חשוב על היבט א' שיתפשט עם מאפיינים בשם "deps" אפשר להחיל את ההיבט הזה על יעד X, וכתוצאה מכך צומת של אפליקציית היבט A(X). במהלך השימוש בו, סימן A מוחל באופן חוזר על כל היעדים שאליהם X מתייחס במאפייני ה-"deps" (כל המאפיינים ברשימת ההדבקה של A').
כלומר, אם תבצעו היבט יחיד של החלת A על יעד X, תקבלו מירכאות &בתרשים
איור 2. יצירת תרשים בעזרת היבטים.
הקצוות היחידים המוצללים הם הקצוות לאורך המאפיינים בקבוצת ההפצה, כך שקצה runtime_deps
לא מופיע בדוגמה
הזו. לאחר מכן, פונקציית יישום היבט מופעלת בכל הצמתים
בתרשים הצלל, בדומה לאופן שבו הטמעות של כללים בצמתים של התרשים המקורי.
דוגמה פשוטה
הדוגמה הזו מדגימה איך מדפיסים באופן חוזר את קובצי המקור של כלל מסוים, וכל התלויים בו, שיש להם מאפיין deps
. הוא מראה
הטמעה של היבט, הגדרת היבט וכיצד להפעיל את ההיבט
בשורת הפקודה Bazel.
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
נחלק את הדוגמה לחלקים שלה ונבחן כל אחד מהם בנפרד.
הגדרת יחס גובה-רוחב
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
הגדרות יחס גובה-רוחב דומות להגדרות של כללים, והן נקבעות באמצעות הפונקציה aspect
.
בדיוק כמו הכלל, יש לישות פונקציית הטמעה במקרה הזה
_print_aspect_impl
.
attr_aspects
היא רשימה של מאפייני כללים שבמהלך ההפצה שלהם.
במקרה כזה, ההיבט יופץ לאורך המאפיין deps
של הכללים שעליהם הוא חל.
ארגומנט נפוץ נוסף עבור attr_aspects
הוא ['*']
שיפיץ את הרוחב לכל המאפיינים של כלל.
הטמעת אפליקציה
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
פונקציות היישום של תכונות דומות לפונקציות היישום של הכלל. הם מחזירים ספקים, יכולים ליצור פעולות, ולקבל שני ארגומנטים:
target
: היעד שעליו רוצים להחיל את ההיבט.ctx
: אובייקטctx
שניתן להשתמש בו כדי לגשת למאפיינים וליצור פלט ופעולות.
פונקציית ההטמעה יכולה לגשת למאפיינים של כלל היעד דרך
ctx.rule.attr
. היא יכולה לבדוק ספקים שסופקו על ידי היעד שעליו הם חלים (באמצעות הארגומנט target
).
יש לציין היבטים כדי להחזיר רשימה של ספקים. בדוגמה הזו, ההיבט לא מספק דבר, כך שהוא מחזיר רשימה ריקה.
הפעלת ההיבט באמצעות שורת הפקודה
הדרך הפשוטה ביותר להחיל היבט היא בשורת הפקודה באמצעות הארגומנט
--aspects
. בהנחה שהיבט זה הוגדר בקובץ בשם print.bzl
כך:
bazel build //MyExample:example --aspects print.bzl%print_aspect
המערכת תחיל את הprint_aspect
על היעד example
ואת כל כללי היעד שאפשר לגשת אליהם רק דרך המאפיין deps
.
הדגל --aspects
מקבל ארגומנט אחד, שהוא מפרט של ההיבט בפורמט <extension file label>%<aspect top-level name>
.
דוגמה מתקדמת
הדוגמה הבאה ממחישה שימוש בהיבט של כלל יעד שסופר קבצים ביעדים, וכך עשוי לסנן אותם לפי תוסף. המדריך מסביר איך להשתמש בספק כדי להחזיר ערכים, איך להשתמש בפרמטרים כדי להעביר ארגומנט בהטמעת היבט ואיך להפעיל היבט מתוך כלל.
קובץ אחד (file_count.bzl
):
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
קובץ אחד (BUILD.bazel
):
load('//:file_count.bzl', 'file_count_rule')
cc_library(
name = 'lib',
srcs = [
'lib.h',
'lib.cc',
],
)
cc_binary(
name = 'app',
srcs = [
'app.h',
'app.cc',
'main.cc',
],
deps = ['lib'],
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
הגדרת יחס גובה-רוחב
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
הדוגמה הזו מראה איך ההיבט מתפשט באמצעות המאפיין deps
.
attrs
מגדיר קבוצת מאפיינים של היבט. מאפיינים של היבט ציבורי הם מסוג string
ונקראים פרמטרים. בפרמטרים צריך לצייןvalues
מאפיין. בדוגמה הזאת יש פרמטר שנקרא extension
מותר להשתמש בערך '*
', 'h
' או 'cc
' בתור ערך.
ערכי הפרמטרים של ההיבט נלקחים ממאפיין המחרוזת עם אותו שם
לכלל שמבקש את ההיבט (ניתן לעיין בהגדרה של file_count_rule
).
לא ניתן להשתמש בהיבטים עם פרמטרים דרך שורת הפקודה, כי אין
תחביר.
בנוסף, מותר להשתמש במאפיינים במאפיינים פרטיים label
או
label_list
. ניתן להשתמש במאפיינים של תוויות פרטיות כדי לציין תלויות
בכלים או בספריות שנדרשות עבור פעולות שנוצרו על ידי היבטים. בדוגמה הזו
לא מוגדר מאפיין פרטי, אבל קטע הקוד הבא
מדגים איך אפשר להעביר בכלי היבט:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
הטמעת אפליקציה
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
ממש כמו פונקציית הטמעת כלל, פונקציית הטמעה של יחס גובה-רוחב מחזירה מבנה של ספקים שנגישים לתלויים שלו.
בדוגמה הזו, FileCountInfo
מוגדר כספק עם שדה אחד
ב-count
. השיטה המומלצת היא להגדיר במפורש את השדות של ספק באמצעות המאפיין fields
.
קבוצת הספקים של אפליקציית A(X) היא איגוד של ספקים
שמגיעים מהחלת כלל על יעד X ועל
הטמעת ההיבט A. הספקים שמטמיעים את הכללים נוצרים
וקפואים לפני שמחילים אותם, ולא ניתן לשנות אותם מהיבטים שונים. יש שגיאה אם יעד והיבט שמוחל עליו
נותנים לספק מאותו סוג, למעט הסוג OutputGroupInfo
(שממוזג, כל עוד
הכלל וה היבט מציינים קבוצות פלט שונות)
InstrumentedFilesInfo
(ולמשל מההיבט). פירוש הדבר הוא שייתכן שהטמעות של רכיבים אף פעם לא יחזרו DefaultInfo
.
הפרמטרים והמאפיינים הפרטיים מועברים במאפיינים של
ctx
. דוגמה זו מתייחסת לפרמטר extension
וקובע
אילו קבצים יש לספור.
במקרה של ספקים חוזרים, ערכי המאפיינים שלפיהם
מופצים ההיבט (מהרשימה attr_aspects
) מוחלפים
בתוצאות של יישום ההיבט. לדוגמה, אם ביעד
X יש את Y ו-Z בשטחי השיא שלו, הערך ctx.rule.attr.deps
עבור A(X) יהיה [A(Y), A(Z)].
בדוגמה הזו, ctx.rule.attr.deps
הם אובייקטים של יעד שהם
תוצאות של החלת ההיבט על 'deps' של היעד המקורי
שבו ההיבט הוחל.
בדוגמה, ההיבט מקבל גישה אל הספק FileCountInfo
מתלויי היעד וצוברים את מספר הקבצים העקיפים הכולל.
הפעלת ההיבט מכלל
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
הטמעת הכלל מדגימה איך לגשת אל FileCountInfo
באמצעות ctx.attr.deps
.
ההגדרה של הכלל מדגימה איך להגדיר פרמטר (extension
)
ולהעניק לו ערך ברירת מחדל (*
). לתשומת ליבך, ערך ברירת המחדל לא היה אחד מהערכים &&39;cc
','h
' או '*
'
הפעלת היבט באמצעות כלל יעד
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
דוגמה זו ממחישה איך מעבירים את הפרמטר extension
אל היבט
דרך הכלל. מאחר שלפרמטר extension
יש ערך ברירת מחדל בהטמעה של
הכלל, extension
ייחשב כפרמטר אופציונלי.
כשבונים את היעד file_count
, המערכת שלנו מבצעת הערכה של ההיבט הזה
בעצמו, ואת כל היעדים שאפשר לגשת אליהם באופן חוזר דרך deps
.