יצירת עובדים קבועים

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

שרת Bazel יוצר קשר עם העובד באמצעות stdin/stdout. הוא תומך בשימוש במאגרי נתונים או במחרוזות JSON.

הטמעת העובד כוללת שני חלקים:

איך הופכים את העובד

עובד קבוע מקפיד על מספר דרישות:

  • היא נקראה WorkRequests מה-stdin שלה.
  • היא כותבת את WorkResponses (ורק WorkResponse) ל-stdout שלה.
  • היא מקבלת את הדגל --persistent_worker. Wrapper חייב לזהות את סימון שורת הפקודה --persistent_worker ולהפוך אותו לרציף רק אם הדגל הזה מועבר, אחרת עליו לעשות הידור אחד של יציאה ולצאת.

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

בקשות עבודה

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

הערה: במפרט מאגר הנתונים של הפרוטוקול נעשה שימוש ב- "snake case" (request_id). בפרוטוקול JSON נעשה שימוש ב- " Capel case" (requestId). במסמך הזה נעשה שימוש בנרתיק הגמל בדוגמאות של JSON, אבל לא מדובר בנרתיק נחשים בדיבור על השדה, ללא קשר לפרוטוקול.

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

ניתן להשתמש בשדה verbosity האופציונלי כדי לבקש פלט ניפוי באגים נוסף מהעובד. כל העבודה היא על העובד ועל אופן הפלט. ערכים גבוהים יותר מציינים פלט מפורט יותר. העברת הסימון --worker_verbose ל-Bazel מגדירה את השדה verbosity ל-10, אבל אפשר להשתמש בערכים קטנים או גדולים יותר באופן ידני כדי לקבל ערכי פלט שונים באופן ידני.

השדה האופציונלי sandbox_dir נמצא בשימוש רק על ידי עובדים שתומכים בארגז חול עם ארגז חול.

תגובות בעבודה

ב-WorkResponse יש מזהה בקשה, קוד יציאה שהוא אפס או אפס, ומחרוזת פלט שמתארת את כל השגיאות בעיבוד או בהפעלה של הבקשה. השדה output מכיל תיאור קצר. אפשר לכתוב יומנים מלאים אל העובד stderr. מאחר שהעובדים יכולים לכתוב רק WorkResponses עד stdout, בדרך כלל העובד מפנה את stdout כל הכלים שמשתמשים בו כדי stderr

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

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

{
  "requestId" : 12,
}

request_id מתוך 0 מציין בקשה "singleplex" , שנעשה בו שימוש כאשר לא ניתן לעבד בקשה זו במקביל לבקשות אחרות. השרת מבטיח שהעובד הנתון מקבל בקשות עם request_id 0 או רק request_id גדול מאפס. בקשות חד-פעמיות נשלחות באופן סידורי, לדוגמה אם השרת לא שולח בקשה נוספת עד לקבלת תגובה (למעט בקשות לביטול, ראו בהמשך).

Notes

  • לפני כל מאגר נתונים מופיע אורך האורך שלו בפורמט varint (מידע נוסף זמין כאן: MessageLite.writeDelimitedTo().
  • אינדיקטור גודל אינו מופיע לפני בקשות ותגובות של JSON.
  • בקשות JSON שומרות על מבנה זהה לזה של הפרוטובוב, אבל משתמשות בקובץ JSON סטנדרטי ומשתמשות באותיות גמל עבור כל שמות השדות.
  • כדי לשמור על מאפייני תאימות לאחור וקדמיים כמו פרוטובוף, עובדי JSON חייבים להכיל שדות לא מוכרים בהודעות האלה ולהשתמש בברירות המחדל של Protobuf לערכים חסרים.
  • Bazel מאחסנת בקשות כפרוטוקולים וממירה אותן לפורמט JSON באמצעות פורמט JSON ל-protobuf

ביטול

עובדים יכולים לאפשר ביטול של בקשות עבודה לפני שהם מסיימים. אפשרות זו שימושית במיוחד בהפעלה דינמית, שבה ניתן לעצור באופן קבוע את ביצוע הפעולה המקומית על ידי ביצוע מהיר יותר מרחוק. כדי לאשר את הביטול, צריך להוסיף את supports-worker-cancellation: 1 לשדה execution-requirements (ראו בהמשך) ולהגדיר את הסימון --experimental_worker_cancellation.

בקשת ביטול היא WorkRequest עם קבוצת השדות cancel (ובדומה לכך, תגובת ביטול היא WorkResponse עם קבוצת השדות was_cancelled). השדה היחיד שחייב להיות בבקשת ביטול או תגובה הוא request_id, המציין איזו בקשה לבטל. השדה request_id יהיה 0 לעובדי יחידה או request_idשל WorkRequest שנשלח בעבר ל-Multiplex. השרת יכול לשלוח בקשות ביטול לבקשות שהעובד כבר השיב להן, ובמקרה כזה יש להתעלם מבקשת הביטול.

כל הודעה ללא ביטול של WorkRequest חייבת לענות פעם אחת בלבד, בין שהיא בוטלה ובין שלא. לאחר שהשרת שלח בקשת ביטול, העובד יכול להגיב עם WorkResponse עם השדה request_id והשדה was_cancelled מוגדר כ-true. אפשר לשלוח גם WorkResponse רגיל, אבל המערכת תתעלם מהשדות output ו-exit_code.

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

יצירת כלל שמשתמש בעובד

תצטרכו גם ליצור כלל שיוצר פעולות לביצוע. יצירת כלל Starlark שמשתמש בעובד היא כמו יצירת כלל אחר.

בנוסף, הכלל צריך להכיל הפניה לעובד עצמו, ויש דרישות מסוימות לגבי הפעולות שהוא יוצר.

הפניה לעובד

הכלל שמשתמש בעובד צריך להכיל שדה שמתייחס לעובד עצמו, ולכן צריך ליצור מופע של כלל \*\_binary כדי להגדיר את העובד. אם העובד שלך נקרא MyWorker.Java, זהו הכלל המשויך:

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

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

אם הקובץ הבינארי של העובדים נמצא בחבילה בשם "work", ברמה העליונה של ה-build, זו יכולה להיות הגדרת המאפיין:

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

ב-cfg = "exec" יש לציין שהעובד חייב לפעול בפלטפורמת ההפעלה ולא בפלטפורמת היעד (כלומר, העובד משמש ככלי במהלך ה-build).

דרישות בעבודה

הכלל שבו נעשה שימוש בעובד יוצר פעולות לביצוע. לפעולות האלה יש כמה דרישות.

  • השדה "arguments". זו רשימה של מחרוזות, שכולן מלבד הארגומנטים שהועברו לעובד עם ההפעלה. הרכיב האחרון ברשימה "arguments" הוא הארגומנט של flag-file (@preorder). העובדים קוראים את הארגומנטים מקובץ הדגל שצוין על בסיס כל בקשה בנפרד. הכלל שלך יכול לכתוב ארגומנטים שאינם מסוג אתחול עבור העובד לקובץ הסימון הזה.

  • השדה "execution-requirements", שבו נדרש מילון שמכיל את "supports-workers" : "1", "supports-multiplex-workers" : "1" או את שניהם.

    השדות '"ארגומנטים" ו-"ביצוע-דרישות" נדרשים לכל הפעולות הנשלחות לעובדים. נוסף על כך, הפעולות שעובדי JSON צריכים לבצע צריך לכלול את "requires-worker-protocol" : "json" בשדה הדרישות לביצוע. "requires-worker-protocol" : "proto" היא גם דרישה חוקית לביצוע, למרות שהיא לא נדרשת לעובדי טיפוס, כי זו ברירת המחדל.

    אפשר גם להגדיר worker-key-mnemonic בדרישות לביצוע. השיטה הזו יכולה להיות שימושית אם אתם משתמשים שוב בקובץ הפעלה עבור מספר סוגי פעולות ואתם רוצים להבדיל בין פעולות של אותו עובד.

  • יש לשמור בספרייה העובדת את הקבצים הזמניים שנוצרו במהלך הפעולה. פעולה זו מפעילה ארגז חול.

בהנחה שהגדרת כלל עם "worker" המאפיין שתואר למעלה, בנוסף למאפיין "srcs" שמייצג את ערכי הקלט, מאפיין "פלט" שמייצג את התפוקות, ומאפיין "args" שמייצג את העובד מפעיל את האתחול, ייתכן שהשיחה אל ctx.actions.run תהיה:

ctx.actions.run(
  inputs=ctx.files.srcs,
  outputs=[ctx.outputs.output],
  executable=ctx.executable.worker,
  mnemonic="someMnemonic",
  execution_requirements={
    "supports-workers" : "1",
    "requires-worker-protocol" : "json"},
  arguments=ctx.attr.args + ["@flagfile"]
 )

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

דוגמאות

בבסיס הקוד של Bazel נעשה שימוש במהדרים של Java בנוסף לעובד JSON לדוגמה שמשמש בבדיקות השילוב שלנו.

אפשר להשתמש בפיגומים כדי להפוך כל כלי המבוסס על Java לעובד על ידי קריאה חוזרת (callback) נכונה.

לדוגמה של כלל שמשתמש בעובד, עיינו בבדיקת השילוב של Bazel' .

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