היבטים

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

ההיבטים מאפשרים הרחבה של תרשימי תלות ב-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&#39).

כלומר, אם תבצעו היבט יחיד של החלת 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.