BazelCon 2022 מגיע בין 16 ל-17 בנובמבר לניו יורק באינטרנט.
הירשמו עוד היום!

ניהול יחסי תלות חיצוניים באמצעות Bzlmod

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

Bzlmod הוא שם הקוד של מערכת התלויות החיצונית שהושקה ב-Bazel 5.0. מטרתו הייתה לטפל בכמה נקודות כאב של המערכת הישנה, שלא ניתן היה לתקן ככל האפשר. כדי לקבל פרטים נוספים, אפשר לעיין בקטע הצהרת בעיה במסמך העיצוב המקורי.

ב-Bazel 5.0, Bzlmod לא מופעל כברירת מחדל. יש לציין את ה--experimental_enable_bzlmod כדי שהישויות הבאות ייכנסו לתוקף. כפי שהשם מרמז, התכונה הזו כרגע ניסיונית. ממשקי API והתנהגויות עשויים להשתנות עד שהתכונה תושק רשמית.

כדי להעביר את הפרויקט ל-Bzlmod, פועלים לפי מדריך ההעברה ל-Bzlmod. דוגמאות נוספות לשימוש ב-Bzlmod במאגר examples.

מודולים של Bazel

המערכת הישנה של תלות תלויה WORKSPACE מבוססת על מאגרים (או מאגר), שנוצרו באמצעות כללי מאגר (או כללי שימור). אמנם מאגר הנתונים הוא עדיין קונספט חשוב במערכת החדשה, אבל מודולים הם היחידות התלויות של הליבה.

מודול הוא פרויקט ב-Bazel שבו יכולות להיות כמה גרסאות, ובכל אחת מהן פורסם מטא-נתונים על מודולים אחרים שתלויים בו. הדבר דומה לקונספטים מוכרים במערכות אחרות של ניהול תלות: Avent, חבילה ל-NPM, ארגז כלים של מטען, מודול של Go וכו'.

המודול מציין את יחסי התלות שלו ב-name וב-version צמדים, במקום בכתובות URL ספציפיות ב-WORKSPACE. בשלב הבא, התלויות בחיפוש במרשם ב-Bazel, כברירת מחדל, ב-Bazel Central Registry. בסביבת העבודה שלכם, כל מודול הופך ל-Repo.

MODULE.bazel

בכל גרסה של כל מודול יש קובץ MODULE.bazel שמצהיר על התלות שלו ועל מטא-נתונים אחרים. זוהי דוגמה בסיסית:

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

צריך למקם את הקובץ MODULE.bazel בשורש של ספריית Workspace, (לצד הקובץ WORKSPACE). בניגוד לקובץ WORKSPACE, אין צורך לציין את יחסי התלויות העקיפים שלכם. במקום זאת, יש לציין רק יחסי תלות ישירים, וקובצי ה-MODULE.bazel התלויים יעברו עיבוד כדי למצוא באופן אוטומטי יחסי תלות.

הקובץ MODULE.bazel דומה ל-BUILD קבצים כי הוא לא תומך בשום זרימת בקרה, וגם אוסר על load הצהרות. התמיכה בקובצי MODULE.bazel היא:

  • הוראות הקשורות לתוספי מודול:
  • פורמט גרסה

    לבזל יש מערכת אקולוגית מגוונת, ופרויקטים משתמשים בסוגים שונים של גרסאות. הפופולרי ביותר הוא SemVer, אבל יש גם פרויקטים בולטים שמשתמשים בסכמות שונות, כמו Abseil, שהגרסאות שלהן מבוססות על תאריך, לדוגמה 20210324.2).

    לכן, Bzlmod משתמשת בגרסה רגועה יותר של מפרט SemVer. ההבדלים כוללים:

    • לפי SemVer, החלק "release" צריך לכלול 3 פלחים בגרסה: MAJOR.MINOR.PATCH. בבזל, הדרישה הזו משוחררת כך שכל מספר של קטעים מותר.
    • ב-SemVer, כל אחד מהפלחים בחלק "release" חייב להיות ספרות בלבד. בבזל, הקשר הזה משוחרר כדי לאפשר גם אותיות, והסמנטיקה של ההשוואה תואמת לקטע "identifiers" בחלק &&;prerelease"
    • בנוסף, הסמנטיקה של העלייה המשמעותית, העיקרית וגרסת התיקון לא נאכפת. (עם זאת, ראו רמת תאימות לקבלת פרטים על האופן שבו אנו מציינים תאימות לאחור.)

    כל גרסה חוקית של SemVer היא גרסה חוקית של מודול Bazel. כמו כן, שתי גרסאות של SemVer a ו-b משווה בין a < b אם אותן השהיות נמצאות בהשוואה בין גרסאות של Bazel.

    רזולוציית גרסה

    הבעיה הקשורה לתליית היהלומים היא עיקר המפתח במרחב הקשור לניהול תלות בגרסאות. נניח שיש לכם את תרשים התלות הבא:

           A 1.0
          /     \
       B 1.0    C 1.1
         |        |
       D 1.0    D 1.1
    

    באיזו גרסה של D צריך להשתמש? כדי לפתור את השאלה הזו, נעשה שימוש באלגוריתם Bzlmod בחירת גרסה מינימלית (MVS) שהוכנסה למערכת של מודול Go. MVS מניחים שכל הגרסאות החדשות של המודול תואמות לאחור, כך שפשוט בוחר את הגרסה הגבוהה ביותר שצוינה בסוג תלוי (D 1.1 בדוגמה שלנו). הוא נקרא "מיני-&&&; כי D 1.1 הוא הגרסה המינימלית שעשויה להתאים לדרישות שלנו. גם אם קיים D 1.2 ומעלה, אנחנו לא בוחרים אותן. זהו יתרון נוסף: בחירת הגרסה היא אמינות גבוהה ולשחזור.

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

    רמת תאימות

    שימו לב שההנחה של MVS' היא אפשרות אפשרית כי היא מתייחסת רק לגרסאות לאחור של מודול שלא תואמות למודול נפרד. במונחים של SemVer, המשמעות היא ש-1.x ו-A 2.x נחשבים למודולים נפרדים, ויכולים לפעול בו-זמנית בתרשים התאימות המפוענח. הדבר מתאפשר, הודות לעובדה שהגרסה הראשית מקודדת בנתיב החבילה ב-Go, כך שאין התנגשויות בזמן הביצוע או בזמן הקישור.

    בבזל, אין לנו הבטחות כאלה. לכן אנחנו צריכים לציין את המספר "major version" כדי לזהות גרסאות לא תואמות לאחור. המספר הזה נקרא רמת תאימות, והוא מצוין בכל גרסת מודול בהוראת module() שלו. אם יש לכם את המידע הזה בהישג יד, נוכל לזהות שגיאה בתרשים שגילינו שיש גרסאות לאותו מודול עם רמות תאימות שונות.

    שמות מאגרים

    בבזאר, לכל תלות חיצונית יש שם של מאגר. לפעמים ניתן להשתמש באותה תלות באמצעות שמות שונים של מאגרים (לדוגמה,@io_bazel_skylibו@bazel_skylib המשמעות היאBazel skylib) או אותו שם מאגר בפרויקטים שונים.

    ב-Bzlmod אפשר ליצור מאגרים על ידי מודולים של Bazel ותוספי מודול. כדי לפתור מחלוקות עם שמות מאגרים, אנחנו אוכפים את מיפוי המאגרים במערכת החדשה. יש שני מושגים חשובים:

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

      • עבור המודל של Bazel: module_name~version
        (דוגמה. @bazel_skylib~1.0.3)
      • עבור המאגר של מודול המידע: module_name~version~extension_name~repo_name
        (דוגמה. @rules_cc~0.0.1~cc_configure~local_config_cc)
    • שם המאגר המקומי: שם המאגר שיהיה בשימוש בקובצי BUILD ו-.bzl במאגר. לאותו תלות יש שמות מקומיים שונים עבור מאגרי נתונים שונים.
      היא נקבעת כך:

      • עבור המודול Bazel יש להזין: module_name כברירת מחדל, או את השם שצוין על ידי מאפיין repo_name ב-bazel_dep.
      • עבור המאגר של מאגר המאגר: שם המאגר שהוזן דרך use_repo.

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

    ירידות דקות

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

    ההטמעה של נקודות השוואה מחמירות מבוססת על מיפוי מאגרים. בעיקרון, מיפוי המאגר של כל מאגר מכיל את כל הקשרים הישירים שלו, שלא ניתן לראות כל מאגר אחר. יחסי תלות גלויים לכל מאגר נקבעים באופן הבא:

    • במאגר של Bazel אפשר לראות את כל המאגרים שמופיעים בקובץ MODULE.bazel דרך bazel_dep וגם use_repo.
    • מאגר תוספים של מודול יכול לראות את כל התלות הגלויות במודול שמספק את התוסף, וגם את כל שאר המאגרים שנוצרו על ידי אותו תוסף.

    מרשמים

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

    רישום אינדקס

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

    רישום של אינדקס חייב להיות בפורמט הבא:

    • /bazel_registry.json: קובץ JSON המכיל מטא-נתונים של הרישום. בשלב זה יש לו רק מפתח אחד, mirrors, שמפרט את רשימת המראות שיש להשתמש בהן להעברה לארכיון.
    • /modules: ספרייה שמכילה ספריית משנה של כל מודול בהרשמה הזו.
    • /modules/$MODULE: ספרייה שמכילה ספריית משנה לכל גרסה של המודול, וגם את הקובץ הבא:
      • metadata.json: קובץ JSON המכיל מידע על המודול, עם השדות הבאים:
        • homepage: כתובת ה-URL של דף הבית של הפרויקט.
        • maintainers: רשימה של אובייקטים של JSON, שכל אחד מהם תואם למידע על התחזוקה של המודול במרשם. שימו לב שזה לא בהכרח זהה למחברים של הפרויקט.
        • versions: רשימה של כל הגרסאות של המודול הזה שניתן למצוא ברישום זה.
        • yanked_versions: רשימה של גרסאות מקושטות של המודול הזה. בשלב זה האפשרות הזו היא לא רלוונטית, אבל בעתיד גרסאות מסוימות יידחו או יגרמו לשגיאה.
    • /modules/$MODULE/$VERSION: ספרייה המכילה את הקבצים הבאים:
      • MODULE.bazel: קובץ MODULE.bazel של גרסת המודול הזו.
      • source.json: קובץ JSON המכיל מידע על אחזור המקור של גרסת המודול הזו, עם השדות הבאים:
        • url: כתובת ה-URL של ארכיון המקור.
        • integrity: סכום היושרה למשאב משנה של הארכיון.
        • strip_prefix: קידומת לספרייה לחילוץ בעת חילוץ ארכיון המקור.
        • patches: רשימת מחרוזות, שכל אחת מהן מציינת שם קובץ לתיקון שחל על הארכיון שחולץ. קובצי התיקון נמצאים בספרייה /modules/$MODULE/$VERSION/patches.
        • patch_strip: זהה לארגומנט --strip של תיקון Unix.
      • patches/: ספרייה אופציונלית המכילה קובצי תיקון.

    Bazel Central Registry

    Bazel Central Registry (BCR) היא רשם אינדקס שנמצא בכתובתbcr.bazel.build. התוכן שלו מגובה דרך ה-Repo של GitHub bazelbuild/bazel-central-registry.

    קהילת ה-BCR מתוחזקת על ידי קהילת Bazel; תורמים יכולים לשלוח בקשות משיכה. עיינו במדיניות ובהליכים של רישום במרשם מרכזי.

    בנוסף לפורמט של רישום אינדקס רגיל, ל-BCR נדרש קובץ presubmit.yml עבור כל גרסת מודול (/modules/$MODULE/$VERSION/presubmit.yml). הקובץ הזה מציין כמה יעדי פיתוח ובדיקה חיוניים שניתן להשתמש בהם כדי לבדוק את התקינות של גרסת המודול הזו. כמו כן, הוא משמש את צינורות עיבוד הנתונים של ה-BCR כדי לוודא את יכולת הפעולה ההדדית בין המודולים ב-BCR.

    בחירת מרשמים

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

    תוספי מודול

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

    תוספי המודול מוגדרים בקובצי .bzl, בדיוק כמו כללי repo או WORKSPACE פקודות מאקרו. הם לא מופעלים באופן ישיר. במקום זאת, כל מודול יכול לציין קטעי נתונים שנקראים תגים כדי לקרוא אותם. לאחר מכן, כאשר הרזולוציה של גרסת המודול מסתיימת, התוספים המודולים פועלים. כל תוסף מופעל פעם אחת לאחר רזולוציית המודול (ממש לפני שנוצרת גרסת build), וקוראים את כל התגים שמשויכים אליו בכל תרשים התלות.

              [ A 1.1                ]
              [   * maven.dep(X 2.1) ]
              [   * maven.pom(...)   ]
                  /              \
       bazel_dep /                \ bazel_dep
                /                  \
    [ B 1.2                ]     [ C 1.0                ]
    [   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
    [   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
                \                  /
       bazel_dep \                / bazel_dep
                  \              /
              [ D 1.4                ]
              [   * maven.dep(Z 1.4) ]
              [   * cargo.dep(Q 1.1) ]
    

    בתרשים התלות לדוגמה שלמעלה, A 1.1 ו-B 1.2 הם מודולים של Bazel. אפשר לחשוב על כל אחד מהם כקובץ MODULE.bazel. כל מודול יכול לציין תגים מסוימים עבור תוספי מודול, וחלקם מפורטים עבור התוסף "maven" וחלק מהם מצוינים עבור "cargo" תרשים התאימות הזה סופי (לדוגמה, אולי ל-B 1.2 יש בפועל bazel_dep ב-D 1.3 אבל שודרג ל-D 1.4 בגלל C), התוספים "Maven" פועלים. בנוסף, הוא יכול לקרוא את כל התגים של maven.*, ולהשתמש במידע הזה כדי להחליט אילו אתרים ליצור. באופן דומה עבור התוסף "cargo".

    שימוש בתוספים

    תוספים מתארחים במודולים של Bazel עצמה, לכן כדי להשתמש בתוסף במודול, צריך להוסיף תחילה bazel_dep במודול הזה, ואז להפעיל את הפונקציה המובנית של use_extension כדי להרחיב אותו. היעזרו בדוגמה הבאה, קטע טקסט מקובץ ב-MODULE.bazel כדי להשתמש בתוסף היפותטי "ven"שהוגדר במודול rules_jvm_external:

    bazel_dep(name = "rules_jvm_external", version = "1.0")
    maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
    

    אחרי שמעבירים את ההיקף להיקף, ניתן להשתמש במסה-נקודה כדי לציין תגים עבורו. הערה: התגים צריכים להתאים לסכימה המוגדרת על ידי סיווגי התגים התואמים (ראו הגדרת תוסף בהמשך). הנה דוגמה שמציינת כמה תגים של maven.dep ושל maven.pom.

    maven.dep(coord="org.junit:junit:3.0")
    maven.dep(coord="com.google.guava:guava:1.2")
    maven.pom(pom_xml="//:pom.xml")
    

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

    use_repo(
        maven,
        "org_junit_junit",
        guava="com_google_guava_guava",
    )
    

    מאגר המאגר שנוצר על ידי תוסף הוא חלק מה-API שלו, ולכן מבין התגים שציינתם, התוסף "maven" ייצור ה-Repo בשם "org_junit_junit" ובתוסף בשם "com_google_gueva_gueva". באמצעות use_repo, תוכלו לבחור לשנות את השמות שלהם בהקשר של המודול שלכם, למשל: "guaba" כאן.

    הגדרת תוסף

    תוספי מודולים מוגדרים באופן דומה לכללי רפו, באמצעות הפונקציה module_extension. לשניהם יש פונקציית הטמעה, אבל לכלל פונקציות יש כמה מאפיינים, אבל תוספי המודולים יכולים לכלול כמהtag_classes. לכל אחד מהם יש כמה מאפיינים. סוגי התגים מגדירים סכימות לתגים שבהם משתמש התוסף הזה. בהמשך לדוגמה שלנו על התוסף ההיפוטלי "maven" למעלה:

    # @rules_jvm_external//:extensions.bzl
    maven_dep = tag_class(attrs = {"coord": attr.string()})
    maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
    maven = module_extension(
        implementation=_maven_impl,
        tag_classes={"dep": maven_dep, "pom": maven_pom},
    )
    

    ההצהרות האלה מבהירות שניתן לציין את תגי maven.dep ו-maven.pom, באמצעות סכימת המאפיינים שהוגדרה למעלה.

    פונקציית ההטמעה דומה למאקרו WORKSPACE, אלא שהיא מקבלת אובייקט module_ctx שנותן גישה לתרשים התלות וכל התגים הרלוונטיים. לאחר מכן, הפונקציה ליישום צריכה לקרוא לכללי repo כדי ליצור repos:

    # @rules_jvm_external//:extensions.bzl
    load("//:repo_rules.bzl", "maven_single_jar")
    def _maven_impl(ctx):
      coords = []
      for mod in ctx.modules:
        coords += [dep.coord for dep in mod.tags.dep]
      output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
      repo_attrs = process_coursier(output)
      [maven_single_jar(**attrs) for attrs in repo_attrs]
    

    בדוגמה שלמעלה, אנחנו עוברים על כל המודולים בתרשים התלות (ctx.modules), שכל אחד מהם הוא אובייקט bazel_module שהשדה tags שלו חושף את כל תגי maven.* במודול. לאחר מכן אנחנו מפעילים את כלי השירות של CLI כדי לפנות ל-Maven ולפנות לפתרון. לבסוף, אנחנו משתמשים בתוצאת הרזולוציה כדי ליצור מספר מאגרים, באמצעות הכלל ההיפותטי maven_single_jar.