מדריך של Bazel: בניית פרויקט Java

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

זמן סיום משוער: 30 דקות.

מה תלמדו

במדריך הזה תלמדו איך:

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

לפני שמתחילים

התקנת Bazel

כדי להתכונן להדרכה, קודם כל מתקינים את Bazel אם היא עוד לא הותקנה.

התקנת JDK

  1. מתקינים את Java JDK (הגרסה המועדפת היא 11, אבל יש תמיכה בגרסאות 8 עד 15).

  2. מגדירים את משתנה הסביבה JAVA_HOME כך שיצביע על JDK.

    • ב-Linux/MacOS:

      export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
      
    • ב-Windows:

      1. פתיחת לוח הבקרה.
      2. "System and Security" > "System" > " Advanced System Settings" > "Advanced" tab > "Environment Variables..." .
      3. ברשימה "משתנים ו&משתמש; (הרשימה שבחלק העליון), לוחצים על "חדש...".
      4. בשדה "Variable name", מזינים JAVA_HOME.
      5. לוחצים על &מירכאות. מעיינים בספרייה...
      6. עוברים לספריית JDK (לדוגמה C:\Program Files\Java\jdk1.8.0_152).
      7. לוחצים על "OK"בכל חלונות הדו-שיח.

קבלת הפרויקט לדוגמה

אחזור פרויקט לדוגמה ממאגר GitHub של Bazel&39:

git clone https://github.com/bazelbuild/examples

הפרויקט לדוגמה של המדריך הזה נמצא בספרייה examples/java-tutorial והוא מוגדר כך:

java-tutorial
├── BUILD
├── src
│   └── main
│       └── java
│           └── com
│               └── example
│                   ├── cmdline
│                   │   ├── BUILD
│                   │   └── Runner.java
│                   ├── Greeting.java
│                   └── ProjectRunner.java
└── WORKSPACE

Build עם Bazel

הגדרת סביבת העבודה

כדי ליצור פרויקט, צריך להגדיר את סביבת העבודה שלו. 'סביבת עבודה' היא ספרייה ששומרת את קובצי המקור של הפרויקט ואת תוצרי ה-build של Bazel&39. הוא גם מכיל קבצים ש-Bazel מזהה כמיוחדים:

  • הקובץ WORKSPACE, שמזהה את הספרייה והתוכן שלה כסביבת עבודה על בסיס ושוכן בבסיס מבנה הספרייה של הפרויקט,

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

כדי להגדיר ספרייה כסביבת עבודה של Bazel, יש ליצור בספרייה הזו קובץ ריק בשם WORKSPACE.

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

הבנת קובץ ה-BUILD

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

מומלץ לעיין בקובץ java-tutorial/BUILD:

java_binary(
    name = "ProjectRunner",
    srcs = glob(["src/main/java/com/example/*.java"]),
)

בדוגמה שלנו, היעד ProjectRunner משקף את הכלל המובנה של Bazel'java_binary. הכלל מורה ל-Bazel לבנות קובץ .jar וסקריפט מעטפת של wrapper (שניהם נקראים על שם היעד).

המאפיינים ביעד מציינים במפורש את האפשרויות והתלויות שלו. המאפיין name הוא מאפיין חובה, אבל יש מאפיינים אופציונליים. לדוגמה, ביעד הכלל ProjectRunner, name הוא שם היעד, srcs מציין את קובצי המקור שבהם Bazel משתמשת כדי לבנות את היעד, ו-main_class מציין את הסיווג שמכיל את השיטה הראשית. (ייתכן ששמת לב לכך שהדוגמה שלנו משתמשת ב-glob כדי להעביר קבוצה של קובצי מקור ל-Bazel, במקום לפרט אותם בנפרד.)

יצירת הפרויקט

כדי ליצור פרויקט לדוגמה, עוברים לספרייה java-tutorial ומריצים:

bazel build //:ProjectRunner

בתווית היעד, החלק // הוא המיקום של הקובץ BUILD ביחס לשורש סביבת העבודה (במקרה הזה, שורש הבעיה), ו-ProjectRunner הוא שם היעד בקובץ BUILD. (מידע נוסף על תוויות יעד מופיע בסוף המדריך.)

Bazel מייצרת פלט דומה לפלט הבא:

   INFO: Found 1 target...
   Target //:ProjectRunner up-to-date:
      bazel-bin/ProjectRunner.jar
      bazel-bin/ProjectRunner
   INFO: Elapsed time: 1.021s, Critical Path: 0.83s

מזל טוב, לבנות את היעד הראשון שלך ב-Bazel! בתוצאות הבזל יש פלטות בספרייה bazel-bin שבבסיס סביבת העבודה. מעיינים בתוכן שלו כדי לקבל מושג לגבי מבנה הפלט של Bazel&39.

עכשיו יש לבדוק את הקובץ הבינארי שבנה לאחרונה:

bazel-bin/ProjectRunner

בדיקת תרשים התלות

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

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

bazel query  --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph

הפקודה שלמעלה מורה ל-Bazel לחפש את כל יחסי התלות של היעד //:ProjectRunner (לא כולל יחסי תלות של מראים ומשתמעים) ולעצב את הפלט כתרשים.

לאחר מכן מדביקים את הטקסט ב-GraphViz.

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

תרשים תלות של היעד 'ProjectRunner'

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

צמצום גרסת ה-Bazel

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

ציון יעדי build מרובים

אפשר לפצל את גרסת הפרויקט לדוגמה לשני יעדים. אתם צריכים להחליף את התוכן של הקובץ java-tutorial/BUILD בתוכן הבא:

java_binary(
    name = "ProjectRunner",
    srcs = ["src/main/java/com/example/ProjectRunner.java"],
    main_class = "com.example.ProjectRunner",
    deps = [":greeter"],
)

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
)

בתצורה הזו, בזל בונה תחילה את הספרייה greeter, ואז את הקובץ הבינארי של ProjectRunner. המאפיין deps ב-java_binary מיידע את בזל על כך שספריית greeter נדרשת לבנות את הקובץ הבינארי של ProjectRunner.

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

bazel build //:ProjectRunner

Bazel מייצרת פלט דומה לפלט הבא:

INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
  bazel-bin/ProjectRunner.jar
  bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s

עכשיו יש לבדוק את הקובץ הבינארי שבנה לאחרונה:

bazel-bin/ProjectRunner

אם משנים עכשיו את ProjectRunner.java ובונים מחדש את הפרויקט, Bazel רק מהדר את הקובץ.

מהתרשים לפי תלות, אפשר לראות שהקלט של ProjectRunner תלוי באותם הנתונים להזין, אבל המבנה של ה-build שונה:

תרשים תלות של יעד 'ProjectRunner' לאחר הוספת תלות

סיימת ליצור את הפרויקט עם שני יעדים. היעד ProjectRunner יוצר שני קובצי מקור ותלוי ביעד אחד נוסף (:greeter), שיוצר קובץ מקור אחד נוסף.

שימוש בחבילות מרובות

עכשיו נפצל את הפרויקט לכמה חבילות. אם תעיינו בספרייה של src/main/java/com/example/cmdline, תוכלו לראות שהיא מכילה גם קובץ BUILD, וגם קובצי מקור מסוימים. לכן, בבזל, סביבת העבודה כוללת עכשיו שתי חבילות: //src/main/java/com/example/cmdline ו-// (כי יש קובץ BUILD בבסיס של סביבת העבודה).

מומלץ לעיין בקובץ src/main/java/com/example/cmdline/BUILD:

java_binary(
    name = "runner",
    srcs = ["Runner.java"],
    main_class = "com.example.cmdline.Runner",
    deps = ["//:greeter"],
)

היעד runner תלוי ביעד greeter בחבילה // (ולכן תווית היעד //:greeter) – ה-Bazel יודעת זאת באמצעות המאפיין deps. אנחנו מציגים את תרשים התלות:

תרשים תלות של היעד 'runner'

עם זאת, כדי שה-build יצליח, עליך לתת באופן מפורש את היעד runner ב-//src/main/java/com/example/cmdline/BUILD ליעדים ב-//BUILD באמצעות המאפיין visibility. הסיבה לכך היא שיעדי ברירת המחדל גלויים רק ליעדים אחרים באותו קובץ BUILD. (Bazel משתמשת בניראות של יעדים כדי למנוע בעיות כמו ספריות עם פרטים להטמעה בממשקי API ציבוריים).

לשם כך, צריך להוסיף את המאפיין visibility ליעד greeter ב-java-tutorial/BUILD כפי שמוצג בהמשך:

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
    visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)

כדי לבנות את החבילה החדשה, תוכלו להריץ את הפקודה הבאה ברמה הבסיסית של סביבת העבודה:

bazel build //src/main/java/com/example/cmdline:runner

Bazel מייצרת פלט דומה לפלט הבא:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner.jar
  bazel-bin/src/main/java/com/example/cmdline/runner
  INFO: Elapsed time: 1.576s, Critical Path: 0.81s

עכשיו יש לבדוק את הקובץ הבינארי שבנה לאחרונה:

./bazel-bin/src/main/java/com/example/cmdline/runner

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

אפשר להשתמש בתוויות כדי לציין יעדים

בקבצים מסוג BUILD ובשורת הפקודה, Bazel משתמשת בתוויות יעד כדי לציין יעדים – לדוגמה, //:ProjectRunner או //src/main/java/com/example/cmdline:runner. התחביר הוא כך:

//path/to/package:target-name

אם היעד הוא יעד לכלל, אז path/to/package הוא הנתיב לספרייה המכילה את הקובץ BUILD, והשדה target-name הוא השם של היעד בקובץ BUILD (המאפיין name). אם היעד הוא יעד, אז path/to/package הוא הנתיב לרמה הבסיסית של החבילה והקובץ target-name הוא השם של קובץ היעד, כולל הנתיב המלא שלו.

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

לדוגמה, לגבי יעדים בקובץ java-tutorial/BUILD לא היה צורך לציין נתיב חבילה, כי שורש העבודה הוא עצמו חבילה (//) ושתי תוויות היעד שלכם היו //:ProjectRunner ו-//:greeter.

עם זאת, עבור היעדים בקובץ //src/main/java/com/example/cmdline/BUILD היה עליכם לציין את מסלול החבילה המלא, //src/main/java/com/example/cmdline, ותווית היעד שלכם הייתה //src/main/java/com/example/cmdline:runner.

חבילת יעד של Java לפריסה

עכשיו נכלול יעד ל-Java לפריסה על ידי בניית הקובץ הבינארי עם כל יחסי תלות בזמן הריצה. כך תוכלו להריץ את הקובץ הבינארי מחוץ לסביבת הפיתוח שלכם.

חשוב לזכור שכלל ה-build של Java_binary יוצר .jar סקריפט של מעטפת wrapper. אפשר לבדוק את התוכן של runner.jar באמצעות הפקודה הבאה:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar

התוכן:

META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class

כפי שאפשר לראות, האפליקציה runner.jar מכילה Runner.class, אבל לא תלויה בה, Greeting.class. הסקריפט של runner ש-Bazel יוצר מוסיף greeter.jarלנתיב הכיתה, כך שאם תשאירו אותו כך, הוא יפעל באופן מקומי, אבל הוא לא יפעל באופן עצמאי במחשב אחר. למזלנו, הכלל java_binary מאפשר לך לבנות קובץ בינארי עצמאי שלפרוס אותו. כדי לבנות אותו, צריך לצרף את _deploy.jar לשם היעד:

bazel build //src/main/java/com/example/cmdline:runner_deploy.jar

Bazel מייצרת פלט דומה לפלט הבא:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s

סיימת לבנות את runner_deploy.jar, וניתן להריץ אותו בנפרד מסביבת הפיתוח, כי הוא מכיל את זמני הריצה הנדרשים. חשוב לצפות בתוכן של ה-JAR הנפרד הזה באמצעות אותה פקודה כמו קודם:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar

התוכן כולל את כל הכיתות הנדרשות להפעלה:

META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class

קריאה נוספת

פרטים נוספים זמינים בכתובת:

בניין שמח!