แอตทริบิวต์บิลด์ที่กำหนดค่าได้

แอตทริบิวต์ที่กำหนดค่าได้ หรือที่เรียกกันทั่วไปว่า select() เป็นฟีเจอร์ของ Bazel ที่ช่วยให้ผู้ใช้สลับค่าของแอตทริบิวต์กฎการสร้างในบรรทัดคำสั่งได้

ฟีเจอร์นี้สามารถใช้ได้ เช่น สำหรับไลบรารีแบบหลายแพลตฟอร์มที่เลือกการใช้งานที่เหมาะสมสำหรับสถาปัตยกรรมโดยอัตโนมัติ หรือสำหรับไบนารีที่กำหนดค่าฟีเจอร์ได้ซึ่งปรับแต่งได้ในระหว่างเวลาบิลด์

ตัวอย่าง

# 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 ที่ "เลือก" การขึ้นต่อกันตามแฟล็กในบรรทัดคำสั่ง โดยเฉพาะอย่างยิ่ง deps จะกลายเป็น

คำสั่ง deps =
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() ทำหน้าที่เป็นตัวยึดตำแหน่งสำหรับค่าที่จะเลือกตาม เงื่อนไขการกำหนดค่า ซึ่งเป็นป้ายกำกับที่อ้างอิงเป้าหมายconfig_setting การใช้ select() ในแอตทริบิวต์ที่กำหนดค่าได้จะทำให้แอตทริบิวต์ใช้ค่าต่างๆ ได้อย่างมีประสิทธิภาพเมื่อเป็นไปตามเงื่อนไขที่แตกต่างกัน

การจับคู่ต้องไม่คลุมเครือ หากมีหลายเงื่อนไขที่ตรงกัน ระบบจะดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้

  • เงื่อนไขทั้งหมดจะแสดงผลเป็นค่าเดียวกัน เช่น เมื่อเรียกใช้ใน Linux x86 โค้ดนี้จะไม่คลุมเครือ {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} เนื่องจากทั้ง 2 สาขาแสดงผลเป็น "hello"
  • `values` ของเงื่อนไขหนึ่งเป็นซูเปอร์เซ็ตแท้ของเงื่อนไขอื่นๆ ทั้งหมด เช่น values = {"cpu": "x86", "compilation_mode": "dbg"} เป็นการเฉพาะเจาะจงที่ไม่คลุมเครือของ values = {"cpu": "x86"}

เงื่อนไขในตัว //conditions:default จะตรงกันโดยอัตโนมัติเมื่อ ไม่มีเงื่อนไขอื่นตรงกัน

แม้ว่าตัวอย่างนี้จะใช้ deps แต่ select() ก็ทำงานได้ดีเช่นกันกับ srcs, resources, cmd และแอตทริบิวต์อื่นๆ ส่วนใหญ่ มีแอตทริบิวต์เพียงไม่กี่รายการเท่านั้นที่ กำหนดค่าไม่ได้ และแอตทริบิวต์เหล่านี้จะมีคำอธิบายประกอบไว้อย่างชัดเจน เช่น แอตทริบิวต์ values ของ config_setting's เองก็กำหนดค่าไม่ได้

select() และการขึ้นต่อกัน

แอตทริบิวต์บางรายการจะเปลี่ยนพารามิเตอร์การสร้างสำหรับการขึ้นต่อกันแบบถ่ายทอดทั้งหมดภายใต้เป้าหมาย เช่น tools ของ genrule จะเปลี่ยน --cpu เป็น CPU ของเครื่องที่เรียกใช้ Bazel (ซึ่งอาจแตกต่างจาก 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 จะผูกการสร้างกับ g_arm.src, tool1 และ x86tool.cc select ทั้ง 2 รายการที่แนบกับ my_genrule ใช้พารามิเตอร์การสร้างของ my_genrule ซึ่งรวมถึง --cpu=arm แอตทริบิวต์ tools จะเปลี่ยน --cpu เป็น x86 สำหรับ tool1 และการขึ้นต่อกันแบบถ่ายทอด select ใน tool1 ใช้พารามิเตอร์การสร้างของ tool1 ซึ่งรวมถึง --cpu=x86

เงื่อนไขการกำหนดค่า

คีย์แต่ละรายการในแอตทริบิวต์ที่กำหนดค่าได้คือการอ้างอิงป้ายกำกับไปยัง config_setting หรือ constraint_value

config_setting เป็นเพียงชุดการตั้งค่า Flag บรรทัดคำสั่งที่คาดไว้ การห่อหุ้มการตั้งค่าเหล่านี้ไว้ในเป้าหมายจะช่วยให้ดูแลรักษาเงื่อนไข "มาตรฐาน" ที่ผู้ใช้อ้างอิงได้จากหลายๆ ที่เป็นเรื่องง่าย

constraint_value รองรับ ลักษณะการทำงานแบบหลายแพลตฟอร์ม

แฟล็กในตัว

แฟล็ก เช่น --cpu มีอยู่ใน Bazel ซึ่งเครื่องมือการสร้างเข้าใจแฟล็กเหล่านี้โดยกำเนิดสำหรับการสร้างทั้งหมดในทุกโปรเจ็กต์ โดยระบุแฟล็กเหล่านี้ด้วย 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 รายงานความคืบหน้าต่อผู้ใช้ เป้าหมายจึงใช้แฟล็กดังกล่าวเพื่อสร้างผลลัพธ์ไม่ได้ ระบบไม่ได้บันทึกชุดแฟล็กที่รองรับไว้อย่างชัดเจน ในทางปฏิบัติ แฟล็กส่วนใหญ่ที่ "สมเหตุสมผล" จะใช้งานได้

แฟล็กที่กำหนดเอง

คุณสามารถสร้างแฟล็กที่เฉพาะเจาะจงกับโปรเจ็กต์ของคุณเองได้ด้วย การตั้งค่าการสร้าง Starlark แฟล็กเหล่านี้จะกำหนดเป็นเป้าหมายการสร้าง ดังนั้น Bazel จึงอ้างอิงแฟล็กเหล่านี้ด้วยป้ายกำกับเป้าหมาย ซึ่งแตกต่างจากแฟล็กในตัว

แฟล็กเหล่านี้จะทริกเกอร์ด้วยแอตทริบิวต์ config_setting's 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"}) หรือแอตทริบิวต์ define_values (define_values = {"foo": "bar"}) โดยระบบรองรับ--define เพื่อความเข้ากันได้แบบย้อนหลังเท่านั้น ดังนั้นจึงควรใช้การตั้งค่าบิลด์ Starlark ทุกครั้งที่ทำได้

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()'s 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 ตรงกันเมื่อมีหลายเงื่อนไขตรงกัน ให้พิจารณาการเชื่อมโยงแบบ AND chaining

การเชื่อมโยงแบบ OR

ลองพิจารณาสิ่งต่อไปนี้

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's 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ของconfig_settingหลายรายการดังนี้

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"],
    }),
)

เป้าหมายต่างๆ สามารถแชร์ :config1_or_2 ในแอตทริบิวต์ต่างๆ ได้ ซึ่งแตกต่างจาก selects.with_or

ระบบจะแสดงข้อผิดพลาดหากมีหลายเงื่อนไขตรงกัน เว้นแต่ว่าเงื่อนไขหนึ่งจะเป็น "การเฉพาะเจาะจง" ที่ไม่คลุมเครือของเงื่อนไขอื่นๆ หรือเงื่อนไขทั้งหมดแสดงผลเป็นค่าเดียวกัน ดูรายละเอียดได้ที่ นี่

การเชื่อมโยงแบบ AND

หากต้องการให้สาขา 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"],
    }),
)

การเชื่อมโยงแบบ AND จะแตกต่างจากการเชื่อมโยงแบบ OR ตรงที่ config_setting ที่มีอยู่ไม่สามารถ AND ภายใน select ได้โดยตรง คุณต้องห่อหุ้ม `config_setting` เหล่านั้นไว้ใน 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"}, ...)

ซึ่งเป็นเพราะเหตุผล 2 ประการ

ประการแรก มาโครที่จำเป็นต้องทราบเส้นทางที่ select จะเลือก ใช้งานไม่ได้ เนื่องจากระบบจะประเมินมาโครในระยะการโหลดของ Bazel ซึ่งเกิดขึ้นก่อนที่จะทราบค่าแฟล็ก นี่เป็นข้อจำกัดการออกแบบหลักของ Bazel ที่ไม่น่าจะเปลี่ยนแปลงในเร็วๆ นี้

ประการที่สอง มาโครที่เพียงแค่ต้องวนซ้ำเส้นทาง select ทั้งหมด แม้ว่าในทางเทคนิคแล้วจะทำได้ แต่ก็ไม่มี UI ที่สอดคล้องกัน จึงต้องมีการออกแบบเพิ่มเติมเพื่อเปลี่ยนแปลงลักษณะการทำงานนี้

การค้นหาและการค้นหาแบบกำหนดค่าได้ของ Bazel

Bazel query ทำงานในระยะการโหลด ของ Bazel ซึ่งหมายความว่าไม่ทราบว่าเป้าหมายใช้แฟล็กบรรทัดคำสั่งใด เนื่องจากระบบจะประเมินแฟล็กเหล่านั้นในภายหลังในระหว่างการสร้าง (ในระยะการวิเคราะห์) ดังนั้น `query` จึงไม่สามารถระบุได้ว่าจะเลือกสาขา select() ใด

Bazel cquery ทำงานหลังจากระยะการวิเคราะห์ของ Bazel ดังนั้นจึงมี ข้อมูลทั้งหมดนี้และสามารถแสดงผล 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 มากเกินไป

$ 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() ใช้งานได้ ในกฎ ดูรายละเอียดได้ที่ความเข้ากันได้ของกฎ

ปัญหาหลักที่คำถามนี้มักหมายถึงคือ 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({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/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({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "other string",
    }),
)

การสร้างจะล้มเหลวเนื่องจาก sad_macro ประมวลผล select() ไม่ได้

$ 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 จะอ่านแฟล็กบรรทัดคำสั่งของการสร้าง ซึ่งหมายความว่ามีข้อมูลไม่เพียงพอที่จะประเมิน select()

อย่างไรก็ตาม มาโครสามารถส่ง select() เป็น Blob ที่ไม่โปร่งใสไปยังกฎได้ดังนี้

# 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() จึงแสดงผลเป็น "จริง" เสมอ

เนื่องจาก มาโคร (แต่ไม่ใช่กฎ) ตามคำจำกัดความ ไม่สามารถประเมิน 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({
        "//third_party/bazel_platforms/cpu:x86_32": True,
        "//third_party/bazel_platforms/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() เอง ตาม มาตรฐานการออกแบบแบบ Pythonic ออบเจ็กต์ทั้งหมดนอกเหนือจากข้อยกเว้นจำนวนน้อยมาก จะแสดงผลเป็น "จริง" โดยอัตโนมัติ

ฉันอ่าน select() เหมือนกับดิกชันนารีได้ไหม

มาโคร ไม่สามารถประเมิน select() ได้เนื่องจากระบบจะประเมินมาโครก่อนที่ Bazel จะทราบพารามิเตอร์บรรทัดคำสั่งของการสร้าง มาโครอ่านดิกชันนารีของ 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 = {
        "//third_party/bazel_platforms/cpu:x86_32": "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 + "> $@",
    )

ทำไม select() จึงใช้งานไม่ได้กับ bind()

ก่อนอื่น โปรดอย่าใช้ bind() เนื่องจากระบบเลิกใช้งาน `bind()` แล้วและแนะนำให้ใช้ alias() แทน

คำตอบทางเทคนิคคือ bind() เป็นกฎ Repo ไม่ใช่กฎ BUILD

กฎ Repo ไม่มีการกำหนดค่าที่เฉพาะเจาะจง และระบบจะประเมินกฎ Repo ในลักษณะที่แตกต่างจากกฎ 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

แต่จริงๆ แล้ว คุณควรหยุดใช้ bind()

ทำไม select() จึงไม่เลือกสิ่งที่ฉันคาดไว้

หาก //myapp:foo มี select() ที่ไม่เลือกเงื่อนไขที่คุณคาดไว้ ให้ใช้ cquery และ 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 เพื่อรับ ที่ถูกต้องได้ในเอกสารประกอบ cquery

ทำไม 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 ไม่แนะนำให้ทำเช่นนี้ เนื่องจากจะจำกัดบิลด์ของคุณมากเกินไปและทำให้ผู้ใช้สับสนเมื่อเงื่อนไขที่คาดไว้ไม่ตรงกัน