שאילתה ניתנת להגדרה (cquery)

cquery היא וריאציה של query שמטפלת כראוי select() בבניית אפשרויות' השפעות על ה-build

כדי לעשות זאת, אפשר להריץ את התוצאות על-ידי ניתוח ב-Bazel' שמשלב את ההשפעות האלה. לעומת זאת, query פועל מעל התוצאות של שלב הטעינה של Bazel לפני הערכת האפשרויות.

למשל:

$ cat > tree/BUILD <<EOF
sh_library(
    name = "ash",
    deps = select({
        ":excelsior": [":manna-ash"],
        ":americana": [":white-ash"],
        "//conditions:default": [":common-ash"],
    }),
)
sh_library(name = "manna-ash")
sh_library(name = "white-ash")
sh_library(name = "common-ash")
config_setting(
    name = "excelsior",
    values = {"define": "species=excelsior"},
)
config_setting(
    name = "americana",
    values = {"define": "species=americana"},
)
EOF
# Traditional query: query doesn't know which select() branch you will choose,
# so it conservatively lists all of possible choices, including all used config_settings.
$ bazel query "deps(//tree:ash)" --noimplicit_deps
//tree:americana
//tree:ash
//tree:common-ash
//tree:excelsior
//tree:manna-ash
//tree:white-ash

# cquery: cquery lets you set build options at the command line and chooses
# the exact dependencies that implies (and also the config_setting targets).
$ bazel cquery "deps(//tree:ash)" --define species=excelsior --noimplicit_deps
//tree:ash (9f87702)
//tree:manna-ash (9f87702)
//tree:americana (9f87702)
//tree:excelsior (9f87702)

כל תוצאה כוללת מזהה ייחודי (9f87702) של התצורה שהיעד מיועד לה.

מכיוון ש-cquery פועל מעל תרשים היעד שהוגדר. אין לו תובנה בנוגע לחפצים כמו פעולות build או גישה לכללים [test_suite](/reference/be/general#test_suite) מכיוון שהם לא יעדים מוגדרים. לאירוע הקודם, יש לעיין ב-[aquery](/docs/aquery).

תחביר בסיסי

קריאה פשוטה אל cquery נראית כך:

bazel cquery "function(//target)"

ביטוי השאילתה "function(//target)" מורכב מהביטויים הבאים:

  • הפונקציה function(...) היא הפונקציה שרוצים להריץ ביעד. cquery יש תמיכה ברוב הפונקציות של query, וגם כמה פונקציות חדשות.
  • //target הוא הביטוי שמוזן לפונקציה. בדוגמה הזו, הביטוי הוא יעד פשוט. אבל שפת השאילתה גם מאפשרת קינון של פונקציות. דוגמאות מפורטות במדריך לשאילתה.

cquery דורש יעד לפעול דרך השלבים של טעינה וניתוח. אלא אם צוין אחרת, cquery מנתח את היעדים המפורטים בביטוי השאילתה. ראו --universe_scope למידע על שאילתות התלויות ביעדי build ברמה העליונה.

הגדרות אישיות

הקו:

//tree:ash (9f87702)

פירוש הדבר ש//tree:ash נבנה בתצורה עם המזהה 9f87702. ברוב היעדים, מדובר בגיבוב (hash) אטום של ערכי אפשרות ה-build שמגדירים את ההגדרה.

כדי לראות את התוכן המלא של התצורה, יש להריץ:

$ bazel config 9f87702

בהגדרת המארח נעשה שימוש במזהה המיוחד (HOST). קובצי מקור שלא נוצרו, כמו אלו שנמצאים לעיתים קרובות ב-srcs, משתמשים במזהה המיוחד (null) (כי אין צורך להגדיר אותם).

9f87702 הוא קידומת של המזהה המלא. הסיבה לכך היא שמזהים מלאים הם גיבובים מסוג SHA-256, שהם ארוכים וקשה לעקוב אחריהם. cquery מבין כל קידומת חוקית של מזהה מלא, בדומה ל Git גיבובים קצרים (hash). כדי להציג את כל המזהים, יש להריץ את $ bazel config.

הערכה של דפוסי יעד

ל-//foo יש משמעות שונה מבחינת cquery מאשר query. הסיבה לכך היא שב-cquery מתבצעת הערכה של יעדים שהוגדרו ותרשים היצירה עשוי לכלול מספר גרסאות מוגדרות של //foo.

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

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

# Analyzes //foo in the target configuration, but also analyzes
# //genrule_with_foo_as_tool which depends on a host-configured
# //foo. So there are two configured target instances of //foo in
# the build graph.
$ bazel cquery //foo --universe_scope=//foo,//genrule_with_foo_as_tool
//foo (9f87702)
//foo (HOST)

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

אפשר למצוא מידע נוסף על תבניות יעד בתיעוד של דפוסי יעד של query.

פונקציות

מתוך מערך הפונקציות הנתמך על ידי query, cquery תומך בכולם מלבד visible, siblings, buildfiles ו-tests.

הוספנו גם את הפונקציות החדשות הבאות ב-cquery:

תצורה

expr ::= config(expr, word)

האופרטור של config מנסה למצוא את היעד שהוגדר עבור התווית שצוינה בארגומנט הראשון ואת ההגדרה שצוינה בארגומנט השני.

הערכים החוקיים לארגומנט השני הם target, host, null או גיבוב תצורה מותאמת אישית. ניתן לאחזר גיבוב (hash) מ-$ bazel config או מפלט קודם של cquery&33.

למשל:

$ bazel cquery "config(//bar, host)" --universe_scope=//foo
$ bazel cquery "deps(//foo)"
//bar (HOST)
//baz (3732cc8)

$ bazel cquery "config(//baz, 3732cc8)"

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

אפשרויות

אפשרויות build

cquery פועל מעל build רגיל של Bazel, וכך יורש את קבוצת האפשרויות הזמינות במהלך build.

שימוש באפשרויות של שאילתה

--universe_scope (רשימה מופרדת בפסיקים)

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

# x/BUILD
genrule(
     name = "my_gen",
     srcs = ["x.in"],
     outs = ["x.cc"],
     cmd = "$(locations :tool) $< >$@",
     tools = [":tool"],
)
cc_library(
    name = "tool",
)

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

שאילתה היעד נוצר פלט
bazel cquery "//x:tools" //x:tool //x:tool(targetconfig)
bazel cquery "//x:tools" --universe_scope="//x:my_gen" //x:my_gen //x:tool(hostconfig)

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

גם אם אפשר ליצור את כל היעדים בביטוי שאילתה ברמה העליונה, לא כדאי לעשות זאת. לדוגמה, הגדרה מפורשת של --universe_scope יכולה למנוע בניית יעדים מספר רב של פעמים בהגדרות שאינן חשובות לך. זה יכול גם לעזור לציין איזו גרסת הגדרה של יעד שאתם מחפשים (כי בשלב זה לא ניתן לציין את זה בצורה אחרת). עליכם להגדיר את הסימון הזה אם ביטוי השאילתה מורכב יותר מ-deps(//foo).

--implicit_deps (ערך בוליאני, ברירת מחדל=True)

הגדרת הסימון הזה כ-false מסננת את כל התוצאות שאינן מוגדרות במפורש בקובץ BUILD ומוגדרות במקום אחר ב-Bazel. זה כולל סינון של שרשרת כלים.

--tool_deps (ערך בוליאני, ברירת מחדל=True)

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

--include_aspects (ערך בוליאני, ברירת מחדל=True)

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

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

אם תבצעו שאילתה כשהיבטים מושבתים, ייתכן שתיתקלו בבעיה שבה היעד X ייכשל בעת יצירת היעד Y, אבל cquery somepath(Y, X) ו-cquery deps(Y) | grep 'X' לא יחזירו תוצאות כי התלות תלויה בהיבט כלשהו.

פלט פורמטים

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

מעברים

--transitions=lite
--transitions=full

מעברים של תצורה משמשים ליצירת יעדים מתחת ליעדים ברמה העליונה בהגדרות שונות מאלה של היעדים ברמה העליונה.

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

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

פלט ההודעה של הפרוטוקול

--output=proto

אפשרות זו גורמת להדפסת היעדים המתקבלים בטופס אגירת פרוטוקול בינארי. אפשר למצוא את ההגדרה של מאגר הנתונים הזמני בפרוטוקול src/main/protobuf/analysis.proto.

CqueryResult היא ההודעה ברמה העליונה שמכילה את תוצאות השאילתה. יש בו רשימה של ConfiguredTarget הודעות ורשימה של Configuration הודעות. לכל ConfiguredTarget יש configuration_id שהערך שלו שווה לערך של השדה id מההודעה Configuration המתאימה.

--[no]proto:include_Configurations

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

אפשר לעיין בתיעוד של פלט הפרוטו של השאילתה כדי לראות אפשרויות נוספות הקשורות לפרוטוקול.

פלט התרשים

--output=graph

בחירה באפשרות הזאת תייצר פלט כקובץ .dot תואם ל-Graphviz. לפרטים, יש לעיין בquery'sתיעוד של פלט. cquery תומכת גם ב---graph:node_limit וב- --graph:factored.

פלט קבצים

--output=files

באפשרות זו מודפסת רשימה של קובצי פלט שהופקו על ידי כל יעד, שתואם לשאילתה שדומה לרשימה המודפסת בסוף הפעלת bazel build. הפלט מכיל רק את הקבצים שפורסמו בקבוצות הפלט המבוקשות, על פי הסימון של --output_groups לעולם לא מכיל קובצי מקור.

הגדרת פורמט הפלט באמצעות Starlark

--output=starlark

פורמט הפלט הזה מפעיל פונקציה ב-Starlark לכל יעד מוגדר בתוצאת השאילתה, ומדפיס את הערך שמוחזר על ידי השיחה. הדגל --starlark:file מציין את המיקום של קובץ Starlark שמגדיר פונקציה בשם format עם פרמטר יחיד, target. הפונקציה הזו נקראת עבור כל יעד בתוצאת השאילתה. לחלופין, אפשר לציין רק גוף של פונקציה שהוכרז כ-def format(target): return expr באמצעות הסימון --starlark:expr.

'cquery' Starlark ניב

סביבת ה-Querylark של השאילתה שונה מקובץ BUILD או bzl. הוא כולל את כל הפונקציות קבועות והפונקציות המובנות, וגם כמה פונקציות ספציפיות לשאילתה שמפורטות בהמשך, אבל לא את הנתונים (למשל) glob , native או rule, והוא לא תומך בהצהרות טעינה.

build_option(target)

build_options(target) מחזירה מפה שהמפתחות שלה הם מזהי אפשרויות build (ראו תצורות) והערכים שלהם הם ערכי Starlark. יצירת אפשרויות שהערכים שלהן אינם חוקיים של ערכי Starlark מושמטים מהמפה הזו.

אם היעד הוא קובץ קלט, הפונקציה build_options(target) מחזירה 'ללא', מפני שביעדי קובצי הקלט יש הגדרה ריקה.

ספקים(יעד)

providers(target) מחזירה מפה שהמפתחות שלה הם שמות של ספקים (לדוגמה, "DefaultInfo") והערכים שלהם הם ערכי Starlark. ספקים שהערכים שלהם אינם ערכים משפטיים של Starlark מושמטים מהמפה הזו.

דוגמאות

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

  bazel cquery //foo --output=starlark \
    --starlark:expr="' '.join([f.basename for f in target.files.to_list()])"

אפשר להדפיס רשימה מופרדת ברווחים של כל הקבצים שהופקו על ידי יעדים של כללים ב-//bar ובחבילות המשנה שלו:

  bazel cquery 'kind(rule, //bar/...)' --output=starlark \
    --starlark:expr="' '.join([f.path for f in target.files.to_list()])"

יש להדפיס רשימה של שאר הפעולות של //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="[a.mnemonic for a in target.actions]"

אפשר להדפיס רשימה של פלטי הידור שרשומים על ידי cc_library //baz.

  bazel cquery //baz --output=starlark \
    --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"

יש להדפיס את הערך של אפשרות שורת הפקודה --javacopt בעת יצירת //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="build_options(target)['//command_line_option:javacopt']"

הדפסת התווית של כל יעד בפלט אחד בדיוק. בדוגמה הזו נעשה שימוש בפונקציות Starlark המוגדרות בקובץ.

  $ cat example.cquery

  def has_one_output(target):
    return len(target.files.to_list()) == 1

  def format(target):
    if has_one_output(target):
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

הדפסת התווית של כל יעד שהוא בדיוק Python 3. בדוגמה הזו נעשה שימוש בפונקציות Starlark המוגדרות בקובץ.

  $ cat example.cquery

  def format(target):
    p = providers(target)
    py_info = p.get("PyInfo")
    if py_info and py_info.has_py3_only_sources:
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

חילוץ ערך מספק שמוגדר על ידי המשתמש.

  $ cat some_package/my_rule.bzl

  MyRuleInfo = provider(fields={"color": "the name of a color"})

  def _my_rule_impl(ctx):
      ...
      return [MyRuleInfo(color="red")]

  my_rule = rule(
      implementation = _my_rule_impl,
      attrs = {...},
  )

  $ cat example.cquery

  def format(target):
    p = providers(target)
    my_rule_info = p.get("//some_package:my_rule.bzl%MyRuleInfo'")
    if my_rule_info:
      return my_rule_info.color
    return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

שאילתה לעומת שאילתה

cquery ו-query משלימים זה את זה ומצטיינים בנישות שונות. כדי להחליט מה מתאים לך:

  • cquery עוקב אחרי ענפים ספציפיים ב-select() כדי ליצור מודל מדויק של התרשים. query לא יודע/ת איזו ענף בוחר, כך שההערכה היא לכלול את כל הסניפים.
  • cqueryכדי לשמור על דיוק, נדרש יותר מידע מהתרשים מאשר query. באופן ספציפי, מערכת cquery מעריכה יעדים מוגדרים בלבד, אבל מערכת query מעריכה רק יעדים. התהליך הזה לוקח יותר זמן והוא צורך יותר זיכרון.
  • cqueryההבנה של שפת השאילתה גורמת לאי בהירות שquery מונעת. לדוגמה, אם יש "//foo" בשתי תצורות, באיזו תצורה צריך להשתמש ב-cquery "deps(//foo)"? הפונקציה [config](#config) יכולה לעזור בכך.
  • ככלי חדש יותר, אין ל-cquery תמיכה עבור תרחישי שימוש מסוימים. כדי לקבל פרטים נוספים, אפשר לעיין בקטע בעיות מוכרות.

בעיות ידועות

לכל היעדים ש-cquery "builds&quot יש בהם את אותה הגדרה.

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

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

פתרון עקיף: אם ניתן, כדאי להגדיר את --universe_scope להיקף מחמיר יותר. למשל:

# This command attempts to build the transitive closures of both //foo and
# //bar. //bar uses an incoming edge transition to change its --cpu flag.
$ bazel cquery 'somepath(//foo, //bar)'
ERROR: Error doing post analysis query: Top-level targets //foo and //bar
have different configurations (top-level targets with different
configurations is not supported)

# This command only builds the transitive closure of //foo, under which
# //bar should exist in the correct configuration.
$ bazel cquery 'somepath(//foo, //bar)' --universe_scope=//foo

אין תמיכה בדומיין --output=xml.

פלט לא חד-משמעי.

הפונקציה cquery לא מוחקת באופן אוטומטי את תרשים ה-build מפקודות קודמות, ולכן היא נוטה לאסוף תוצאות משאילתות קודמות. לדוגמה, ב-genquery פועל מעבר של המארח במאפיין tools שלו, כלומר הוא מגדיר את הכלים שלו בהגדרת המארח.

בהמשך ניתן לראות את ההשפעות של המעבר.

$ cat > foo/BUILD <<<EOF
genrule(
    name = "my_gen",
    srcs = ["x.in"],
    outs = ["x.cc"],
    cmd = "$(locations :tool) $< >$@",
    tools = [":tool"],
)
cc_library(
    name = "tool",
)
EOF

    $ bazel cquery "//foo:tool"
tool(target_config)

    $ bazel cquery "deps(//foo:my_gen)"
my_gen (target_config)
tool (host_config)
...

    $ bazel cquery "//foo:tool"
tool(host_config)

פתרון עקיף: ניתן לשנות את כל אפשרויות ההפעלה כדי לאלץ ניתוח מחדש של יעדים שהוגדרו. לדוגמה, אפשר להוסיף את --test_arg=&lt;whatever&gt; לפקודת ה-build.

פתרון בעיות

דפוסי יעד חוזרים (/...)

אם תיתקלו ב:

$ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, //foo/...)"
ERROR: Error doing post analysis query: Evaluation failed: Unable to load package '[foo]'
because package is not in scope. Check that all target patterns in query expression are within the
--universe_scope of this query.

פעולה זו מציינת שהחבילה //foo לא נכללת בהיקף למרות ש---universe_scope=//foo:app כוללת אותה. ההגבלה הזו נובעת ממגבלות עיצוב ב-cquery. כפתרון עקיף, ניתן לכלול את //foo/... באופן מפורש בהיקף היקום:

$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"

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

# Replace "//foo/..." with a subshell query call (not cquery!) outputting each package, piped into
# a sed call converting "<pkg>" to "//<pkg>:*", piped into a "+"-delimited line merge.
# Output looks like "//foo:*+//foo/bar:*+//foo/baz".
#
$  bazel cquery --universe_scope=//foo:app "somepath(//foo:app, $(bazel query //foo/...
--output=package | sed -e 's/^/\/\//' -e 's/$/:*/' | paste -sd "+" -))"