מאפייני build הניתנים להגדרה

מאפיינים הניתנים להגדרה, הידועים בשם select(), הם תכונה של Bazel שמאפשרת למשתמשים להחליף את הערכים של מאפייני כלל build בשורת הפקודה.

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

דוגמה

# myapp/BUILD

cc_binary(
    name = "mybinary",
    srcs = ["main.cc"],
    deps = select({
        ":arm_build": [":arm_lib"],
        ":x86_debug_build": [":x86_dev_lib"],
        "//conditions:default": [":generic_lib"],
    }),
)

config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
    },
)

זה מצהיר על cc_binary ש &מירכאות;&chooses באופן ספציפי, deps הופך ל:

Command נקודות נקודות =
bazel build //myapp:mybinary --cpu=arm [":arm_lib"]
bazel build //myapp:mybinary -c dbg --cpu=x86 [":x86_dev_lib"]
bazel build //myapp:mybinary --cpu=ppc [":generic_lib"]
bazel build //myapp:mybinary -c dbg --cpu=ppc [":generic_lib"]

select() משמש כ-placeholder לערך שייבחר על סמך תנאי תצורה, שהם תוויות שמתייחסות ליעדים של config_setting. על ידי שימוש ב-select() במאפיין שניתן להגדרה, המאפיין מקבל בפועל ערכים שונים כאשר מתקיימים תנאים שונים.

ההתאמות צריכות להיות חד-משמעיות: צריך להתקיים תנאי אחד בדיוק. אם מספר תנאים תואמים, התנאי values צריך להיות קבוצת-על של כל התנאים האחרים. לדוגמה, values = {"cpu": "x86", "compilation_mode": "dbg"} היא התמחות חד-משמעית של values = {"cpu": "x86"}. התנאי המובנה //conditions:default תואם באופן אוטומטי אם אין שום דבר אחר.

בדוגמה הזו נעשה שימוש ב-deps, אבל select() פועל היטב גם ב-srcs, resources, ב-cmd וברוב המאפיינים האחרים. רק מספר קטן של מאפיינים לא ניתנים להגדרה, ויש בהם הערות ברורות. לדוגמה, המאפיין config_setting'עצמו values לא ניתן להגדרה.

select() ותלויים

מאפיינים מסוימים משנים את הפרמטרים של ה-build לכל התלויות העקיפות במסגרת יעד. לדוגמה, הפונקציה tools&genrule משנה את --cpu ליחידת העיבוד המרכזית (CPU) של Bazel (שאינה עשויה להיות מורכבת מיחידת עיבוד מרכזית (CPU), ייתכן שהיא שונה מיחידת העיבוד המרכזית (CPU) שהיעד מיועד אליה. פעולה זו נקראת מעבר להגדרות אישיות.

נתון

#myapp/BUILD

config_setting(
    name = "arm_cpu",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

genrule(
    name = "my_genrule",
    srcs = select({
        ":arm_cpu": ["g_arm.src"],
        ":x86_cpu": ["g_x86.src"],
    }),
    tools = select({
        ":arm_cpu": [":tool1"],
        ":x86_cpu": [":tool2"],
    }),
)

cc_binary(
    name = "tool1",
    srcs = select({
        ":arm_cpu": ["armtool.cc"],
        ":x86_cpu": ["x86tool.cc"],
    }),
)

ריצה

$ bazel build //myapp:my_genrule --cpu=arm

במכונה למפתחים של x86, מתבצע חיבור בין build ל-g_arm.src, tool1 ו-x86tool.cc. שני הפרמטרים (select) המצורפים אל my_genrule משתמשים בפרמטרים של my_genrule' הכוללים את --cpu=arm. המאפיין tools משתנה ל---cpu ל-x86 בהתאם לtool1 ולתלויות העקיפות שלו. select ב-tool1 משתמש בפרמטרים של tool1&build, כולל --cpu=x86.

תנאים בקשר להגדרות

כל מפתח במאפיין שניתן להגדרה הוא הפניה לתווית config_setting או constraint_value.

config_setting הוא רק אוסף של הגדרות ניסיוניות צפויות בשורת הפקודה. הכללה של היעדים האלה בטירגוט אחד מקלה על התחזוקה של תנאים "standard" שמשתמשים יכולים להפנות ממספר מקומות.

constraint_value מספקת תמיכה בהתנהגות של פלטפורמות מרובות.

סימונים מובנים

סימונים כמו --cpu מובנים ב-Bazel: כלי ה-build עוזר להבין את כל ה-build בכל הפרויקטים. הערכים האלה מסומנים במאפיין config_setting's values:

config_setting(
    name = "meaningful_condition_name",
    values = {
        "flag1": "value1",
        "flag2": "value2",
        ...
    },
)

flagN הוא שם דגל (ללא --, כך ש-"cpu" במקום "--cpu"). valueN הוא הערך הצפוי של הסימון הזה. :meaningful_condition_name תואם אם כל הרשומה ב-values תואמת. ההזמנה לא רלוונטית.

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

  • values = { "compilation_mode": "opt" } התאמות bazel build -c opt
  • values = { "force_pic": "true" } התאמות bazel build --force_pic=1
  • values = { "force_pic": "0" } התאמות bazel build --noforce_pic

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

סימונים מותאמים אישית

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

ההתראות מופעלות באמצעות המאפיין config_setting' flag_values:

config_setting(
    name = "meaningful_condition_name",
    flag_values = {
        "//myflags:flag1": "value1",
        "//myflags:flag2": "value2",
        ...
    },
)

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

--define הוא תחביר מדור קודם חלופי עבור סימונים מותאמים אישית (לדוגמה --define foo=bar). אפשר לבטא זאת במאפיין values (values = {"define": "foo=bar"}) או במאפיין configure_value (define_values = {"foo": "bar"}). אפשר להשתמש ב---define רק לצורך תאימות. עדיפות להגדרות של BuildStark ככל האפשר.

values, flag_values וdefine_values מבצעים הערכה בנפרד. המאפיין config_setting תואם אם כל הערכים בכולם תואמים.

תנאי ברירת המחדל

התנאי המובנה //conditions:default תואם כאשר אין תנאי אחר תואם.

בגלל הכלל "בהתאמה אחת בלבד" מאפיין שאפשר להגדיר בלי התאמה וללא תנאי ברירת מחדל גורם לשגיאה "no matching conditions". התכונה הזו יכולה להגן מפני כשלים שקטים בהגדרות בלתי צפויות:

# myapp/BUILD

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

cc_library(
    name = "x86_only_lib",
    srcs = select({
        ":x86_cpu": ["lib.cc"],
    }),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //myapp:x86_cpu

כדי לקבל שגיאות ברורות עוד יותר, ניתן להגדיר הודעות מותאמות אישית באמצעות המאפיין select()' no_match_error.

פלטפורמות

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

# myapp/BUILD

sh_binary(
    name = "my_rocks",
    srcs = select({
        ":basalt": ["pyroxene.sh"],
        ":marble": ["calcite.sh"],
        "//conditions:default": ["feldspar.sh"],
    }),
)

config_setting(
    name = "basalt",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

config_setting(
    name = "marble",
    constraint_values = [
        ":white",
        ":metamorphic",
    ],
)

# constraint_setting acts as an enum type, and constraint_value as an enum value.
constraint_setting(name = "color")
constraint_value(name = "black", constraint_setting = "color")
constraint_value(name = "white", constraint_setting = "color")
constraint_setting(name = "texture")
constraint_value(name = "smooth", constraint_setting = "texture")
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")

platform(
    name = "basalt_platform",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

platform(
    name = "marble_platform",
    constraint_values = [
        ":white",
        ":smooth",
        ":metamorphic",
    ],
)

ניתן לציין את הפלטפורמה בשורת הפקודה. הוא מפעיל את config_setting שמכילים קבוצת משנה של הפלטפורמה constraint_values, כדי לאפשר ל-config_setting להתאים בין ביטויים ב-select().

לדוגמה, כדי להגדיר את המאפיין srcs כ-my_rocks ל-calcite.sh, אפשר פשוט להריץ

bazel build //my_app:my_rocks --platforms=//myapp:marble_platform

ללא פלטפורמות, הקטע עשוי להיראות בערך כך

bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic

select() יכול גם לקרוא ישירות את constraint_value:

constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
    name = "my_rocks",
    srcs = select({
        ":igneous": ["igneous.sh"],
        ":metamorphic" ["metamorphic.sh"],
    }),
)

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

הפלטפורמות עדיין בשלבי פיתוח. פרטים נוספים זמינים בתיעוד.

שילוב של select() שניות

המאפיין select יכול להופיע כמה פעמים באותו מאפיין:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"] +
           select({
               ":armeabi_mode": ["armeabi_src.sh"],
               ":x86_mode": ["x86_src.sh"],
           }) +
           select({
               ":opt_mode": ["opt_extras.sh"],
               ":dbg_mode": ["dbg_extras.sh"],
           }),
)

select לא יכול להופיע בתוך select אחר. אם צריך לשייך את selects והמאפיין מקבל יעדים אחרים כערכים, יש להשתמש ביעד ביניים:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":armeabi_mode": [":armeabi_lib"],
        ...
    }),
)

sh_library(
    name = "armeabi_lib",
    srcs = select({
        ":opt_mode": ["armeabi_with_opt.sh"],
        ...
    }),
)

אם יש צורך ב-select כדי להתאים כאשר יש מספר תנאים, מומלץ וגם לשרשראות.

או שרשור

כמה נקודות שכדאי לזכור:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": [":standard_lib"],
        ":config2": [":standard_lib"],
        ":config3": [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

רוב התנאים מעריכים את אותו פילוח, אבל קשה לקרוא את הטקסט בעזרת תחביר. מומלץ שלא לחזור על [":standard_lib"] מספר פעמים.

אפשרות אחת היא להגדיר מראש את הערך כמשתנה BUILD:

STANDARD_DEP = [":standard_lib"]

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": STANDARD_DEP,
        ":config2": STANDARD_DEP,
        ":config3": STANDARD_DEP,
        ":config4": [":special_lib"],
    }),
)

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

לקבלת תמיכה ישירה יותר, ניתן להשתמש באחת מהאפשרויות הבאות:

selects.with_or

המאקרו with_or במודול Skylib' selects תומך בתנאי OR ישירות בתוך select:

load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = selects.with_or({
        (":config1", ":config2", ":config3"): [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

selects.config_setting_group

המאקרו config_setting_group במערכת Skylib's selects תומך OR במספר OR:

load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_or_2",
    match_any = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_or_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

בניגוד ל-selects.with_or, יעדים שונים יכולים לשתף את :config1_or_2 במאפיינים שונים.

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

ושרשור

אם יש לך סניף של select לצורך התאמה כאשר יש התאמה של מספר תנאים, יש להשתמש במאקרו Skylib config_setting_group:

config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_and_2",
    match_all = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_and_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

בניגוד ל-OR, לא ניתן לקבוע config_setting קיימים. AND בתוך select. צריך לעטוף אותם באופן מפורש באמצעות config_setting_group.

הודעות שגיאה בהתאמה אישית

כברירת מחדל, כשאין תנאי תואם, היעד select() מצורף לכשלים עם השגיאה:

ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //tools/cc_target_os:darwin
  //tools/cc_target_os:android

אפשר להתאים אישית את המאפיין הזה באמצעות המאפיין no_match_error:

cc_library(
    name = "my_lib",
    deps = select(
        {
            "//tools/cc_target_os:android": [":android_deps"],
            "//tools/cc_target_os:windows": [":windows_deps"],
        },
        no_match_error = "Please build with an Android or Windows toolchain",
    ),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain

תאימות לכללים

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

# myapp/BUILD

some_rule(
    name = "my_target",
    some_attr = select({
        ":foo_mode": [":foo"],
        ":bar_mode": [":bar"],
    }),
)
$ bazel build //myapp/my_target --define mode=foo

קוד ההטמעה של הכלל רואה את ctx.attr.some_attr כ-[":foo"].

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

select({"foo": "val"}, ...)

אל

select({"foo": "val_with_suffix"}, ...)

יש לכך שתי סיבות.

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

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

שאילתה ושאילתה

Bazel query פועל מעל Bazel&33; בטעינה. פירוש הדבר הוא שהוא לא יודע באיזו שורת פקודה מתבצע סימון של יעד, מכיוון שהסימונים האלה לא נבדקים עד למועד מאוחר יותר (בשלב הניתוח). כדי שניתן יהיה לקבוע אילו סניפים של select() ייבחרו.

Bazel cquery פועל לאחר שלב הניתוח של Bazel&33, ולכן יש לו את כל המידע הזה והוא יכול לפתור באופן מדויק את select().

שקול:

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD

string_flag(
    name = "dog_type",
    build_setting_default = "cat"
)

cc_library(
    name = "my_lib",
    deps = select({
        ":long": [":foo_dep"],
        ":short": [":bar_dep"],
    }),
)

config_setting(
    name = "long",
    flag_values = {":dog_type": "dachshund"},
)

config_setting(
    name = "short",
    flag_values = {":dog_type": "pug"},
)

query אומדנים משוערים של :my_lib&#39:

$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep

בעוד שהמדד cquery מציג את תלויותיו המדויקים:

$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep

שאלות נפוצות

למה אי אפשר להשתמש בסלקטורים (+)?

select() כן פועל בכללים! לפרטים נוספים, אפשר לעיין בתאימות לכללים.

הבעיה העיקרית בדרך כלל היא השאלה ש-select() לא פועל בפקודות מאקרו. הכללים האלה שונים מכללים. כדאי לעיין בתיעוד בנושא כללים ורכיבי מאקרו כדי להבין את ההבדלים. הנה דוגמה מקצה לקצה:

מגדירים כלל ומאקרו:

# myapp/defs.bzl

# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
    name = ctx.attr.name
    allcaps = ctx.attr.my_config_string.upper()  # This works fine on all values.
    print("My name is " + name + " with custom message: " + allcaps)

# Rule declaration:
my_custom_bazel_rule = rule(
    implementation = _impl,
    attrs = {"my_config_string": attr.string()},
)

# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
    allcaps = my_config_string.upper()  # This line won't work with select(s).
    print("My name is " + name + " with custom message: " + allcaps)

יצירה מיידית של הכלל והמאקרו:

# myapp/BUILD

load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")

my_custom_bazel_rule(
    name = "happy_rule",
    my_config_string = select({
        "//tools/target_cpu:x86": "first string",
        "//tools/target_cpu:ppc": "second string",
    }),
)

my_custom_bazel_macro(
    name = "happy_macro",
    my_config_string = "fixed string",
)

my_custom_bazel_macro(
    name = "sad_macro",
    my_config_string = select({
        "//tools/target_cpu:x86": "first string",
        "//tools/target_cpu:ppc": "other string",
    }),
)

הבנייה נכשלה כי לא ניתן לעבד את select() ב-sad_macro:

$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.

התגובה של sad_macro תטופל בהצלחה:

# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.

לא ניתן לשנות את ההגדרה הזו כי מתבצעת בדיקה של פקודות מאקרו בהגדרה לפני ש-Bazel קוראת את הסימונים של שורת הפקודה של Build. כלומר, אין מספיק מידע כדי להעריך את הערך (#()).

עם זאת, פונקציות מאקרו יכולות להעביר select() בתור blobs אטומות לכללים:

# myapp/defs.bzl

def my_custom_bazel_macro(name, my_config_string):
    print("Invoking macro " + name)
    my_custom_bazel_rule(
        name = name + "_as_target",
        my_config_string = my_config_string,
    )
$ bazel build //myapp:sad_macro_less_sad
DEBUG: /myworkspace/myapp/defs.bzl:23:3: Invoking macro sad_macro_less_sad.
DEBUG: /myworkspace/myapp/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING.

למה select() תמיד מחזירה את הערך true?

מכיוון שפקודות מאקרו (אבל לא כללים) לפי הגדרה לא ניתן להעריך את select()s, כל ניסיון לעשות זאת יוצר בדרך כלל שגיאה:

ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().

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

$ cat myapp/defs.bzl
def my_boolean_macro(boolval):
  print("TRUE" if boolval else "FALSE")

$ cat myapp/BUILD
load("//myapp:defs.bzl", "my_boolean_macro")
my_boolean_macro(
    boolval = select({
        "//tools/target_cpu:x86": True,
        "//tools/target_cpu:ppc": False,
    }),
)

$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.

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

האם אפשר לקרוא את Select() כמו הכתבה?

פקודות מאקרו יכולות't להעריך את הבחירות כי המאקרו מעריך לפני ש-Bazel יודעת מה הפרמטרים של שורת הפקודה של Build' האם הם יכולים לפחות לקרוא את המילון של select()' לדוגמה, כדי להוסיף סיומת לכל ערך?

באופן עקרוני, זה אפשרי, אבל עדיין לא תכונה של Bazel. מה שאפשר לעשות היום הוא להכין מילון ישר ואז להזין אותו select():

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
  for key in select_cmd.keys():
    select_cmd[key] += " WITH SUFFIX"
  native.genrule(
      name = name,
      outs = [name + ".out"],
      srcs = [],
      cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
        + " > $@"
  )

$ cat myapp/BUILD
selecty_genrule(
    name = "selecty",
    select_cmd = {
        "//tools/target_cpu:x86": "x86 mode",
    },
)

$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX

אם ברצונך לתמוך ב-select() וגם בסוגים מקומיים, ניתן לעשות זאת:

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
    cmd_suffix = ""
    if type(select_cmd) == "string":
        cmd_suffix = select_cmd + " WITH SUFFIX"
    elif type(select_cmd) == "dict":
        for key in select_cmd.keys():
            select_cmd[key] += " WITH SUFFIX"
        cmd_suffix = select(select_cmd + {"//conditions:default": "default"})

    native.genrule(
        name = name,
        outs = [name + ".out"],
        srcs = [],
        cmd = "echo " + cmd_suffix + "> $@",
    )

למה't select() פועל עם bind()?

כי bind() הוא כלל WORKSPACE, ולא כלל BUILD.

לכללי Workspace אין הגדרה ספציפית, והם לא מוערכים בדיוק כמו כללי BUILD. לכן, select() ב-bind() לא יכול להעריך בפועל לסניף מסוים.

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

אפשר גם להגדיר נקודת יעד של bind() ל-alias(), אם צריך.

$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)

$ cat BUILD
config_setting(
    name = "alt_ssl",
    define_values = {
        "ssl_library": "alternative",
    },
)

alias(
    name = "ssl",
    actual = select({
        "//:alt_ssl": "@alternative//:ssl",
        "//conditions:default": "@boringssl//:ssl",
    }),
)

בהגדרה הזו אפשר לעבור את --define ssl_library=alternative, וכל יעד התלוי ב-//:ssl או ב-//external:ssl יראה את החלופה שממוקמת בכתובת @alternative//:ssl.

למה' select() שלי בוחר למה לצפות?

אם הפונקציה //myapp:foo כוללת select() שלא בוחר את התנאי הצפוי, משתמשים ב-query וב-bazel config לניפוי באגים:

אם היעד הוא ברמה העליונה ובנייה ב-//myapp:foo, יש להריץ:

$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)

אם אתה בונה יעד נוסף כלשהו מסוג //bar שתלוי ב- myapp:foo במקום כלשהו בגרף שלו, יש להריץ:

$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar   (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)

ה-(12e23b9a2b534a) שליד //myapp:foo הוא גיבוב של ההגדרה שמפתורת את select()&//myapp:foo. אפשר לבדוק את הערכים שלו באמצעות bazel config:

$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
  cpu: darwin
  compilation_mode: fastbuild
  ...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
  linkopt: [-Dfoo=bar]
  ...
}
...

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

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

למה select() לא פועל עם פלטפורמות?

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

למשל:

platform(
    name = "x86_linux_platform",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

בקובץ הזה, BUILD (באיזו select()) צריך להשתמש אם בפלטפורמת היעד יש אילוצים של @platforms//cpu:x86 וגם של @platforms//os:linux, אבל לא מוגדר כאן :x86_linux_platform. למחבר של קובץ ה-BUILD ולמשתמש שהגדיר את הפלטפורמה הנפרדת יכולים להיות רעיונות שונים.

מה עליי לעשות במקום זאת?

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

config_setting(
    name = "is_x86_linux",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_x86_linux": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

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

מה אם אני באמת רוצה select בפלטפורמה?

אם באופן ספציפי יש צורך לבדוק את הפלטפורמה, אפשר לשנות את הערך של הסימון --platforms בconfig_setting:

config_setting(
    name = "is_specific_x86_linux_platform",
    values = {
        "platforms": ["//package:x86_linux_platform"],
    },
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

הצוות של Bazel לא מאשר זאת; הוא מגביל את ה-build ומבלבל את המשתמשים כאשר התנאי הצפוי לא תואם.