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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

แอตทริบิวต์ที่กำหนดค่าได้ หรือที่รู้จักกันโดยทั่วไปในชื่อ 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 ที่ "เลือก" การ Dep ของฟังก์ชันตามแฟล็กที่บรรทัดคำสั่ง กล่าวโดยละเอียดคือ 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 ของ 1 คือชุดขั้นสูงที่เข้มงวดกว่าของผู้อื่น เช่น values = {"cpu": "x86", "compilation_mode": "dbg"} เป็นสาขาเฉพาะของ values = {"cpu": "x86"} ที่ชัดเจน

เงื่อนไขในตัว //conditions:default จะจับคู่โดยอัตโนมัติเมื่อไม่มีอะไรแล้ว

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

select() และทรัพยากร Dependency

แอตทริบิวต์บางอย่างจะเปลี่ยนพารามิเตอร์การสร้างสําหรับทรัพยากร transitive ทั้งหมดภายใต้เป้าหมาย เช่น 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 รองรับลักษณะการทำงานแบบหลายแพลตฟอร์ม

แฟล็กในตัว

Flag อย่างเช่น --cpu มีอยู่ใน Bazel อยู่แล้ว ซึ่งเป็นเครื่องมือบิลด์จะเข้าใจบิลด์ทั้งหมดในทุกโปรเจ็กต์อยู่แล้ว ค่าเหล่านี้ระบุด้วยแอตทริบิวต์ values ของ config_setting

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

flagN คือชื่อ Flag (ไม่มี -- เช่น "cpu" แทน "--cpu") valueN คือค่าที่คาดไว้สำหรับ Flag นั้น :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 รองรับเฉพาะ Flag ที่ส่งผลต่อลักษณะการทํางานของเป้าหมาย ตัวอย่างเช่น ระบบไม่อนุญาตให้ใช้ --show_progress เนื่องจากจะส่งผลต่อวิธีรายงานความคืบหน้าของ Bazel ไปยังผู้ใช้เท่านั้น เป้าหมายจะใช้ Flag ดังกล่าวเพื่อสร้างผลลัพธ์ไม่ได้ เราไม่บันทึกชุด Flag ที่รองรับไว้ ในทางปฏิบัติแล้ว การแจ้งว่าไม่เหมาะสมส่วนใหญ่ที่ "สมเหตุสมผล" จะใช้งานได้

การแจ้งที่กำหนดเอง

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

ซึ่งจะมีการทริกเกอร์ด้วยแอตทริบิวต์ flag_values ของ config_setting

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

ลักษณะการทำงานจะเหมือนกับFlag ในตัว ดูตัวอย่างที่ใช้งานได้ที่นี่

--define เป็นไวยากรณ์แบบเดิมอีกอย่างหนึ่งสำหรับ Flag ที่กำหนดเอง (เช่น --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 จะจับคู่เมื่อไม่มีเงื่อนไขอื่นที่ตรงกัน

เนื่องจากกฎ "ตรงกันทั้งหมด 1 รายการ" แอตทริบิวต์ที่กำหนดค่าได้ซึ่งไม่ตรงกันและไม่มีเงื่อนไขเริ่มต้นจะแสดงข้อผิดพลาด "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

คุณตั้งค่าข้อความที่กำหนดเองโดยใช้แอตทริบิวต์ no_match_error ของ select() เพื่อแก้ไขข้อผิดพลาดที่ชัดเจนยิ่งขึ้นได้

แพลตฟอร์ม

แม้ว่าความสามารถในการระบุ Flag หลายรายการในบรรทัดคำสั่งจะให้ความยืดหยุ่น แต่ก็อาจทำได้ยากหากตั้งค่าแต่ละรายการทีละรายการทุกครั้งที่ต้องการสร้างเป้าหมาย แพลตฟอร์ม ช่วยให้คุณรวมรายการเหล่านี้เป็นแพ็กเกจง่ายๆ ได้

# 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 แบบ Boilerplate เมื่อคุณต้องการตรวจสอบค่าเดี่ยวๆ เท่านั้น

แพลตฟอร์มยังอยู่ระหว่างการพัฒนา ดูรายละเอียดในเอกสารประกอบ

กำลังรวม 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

การทำเชนแบบ OR

ลองพิจารณาสิ่งเหล่านี้

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

เงื่อนไขส่วนใหญ่จะประเมินเป็น dep เดียวกัน แต่ไวยากรณ์นี้อ่านและดูแลรักษาได้ยาก คงจะดีหากไม่ต้องป้อน [":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 ในข้อบังคับ selects ของ Skylib รองรับเงื่อนไข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 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"],
    }),
)

config_setting ที่มีอยู่จะANDภายใน select โดยตรงไม่ได้ ซึ่งต่างจากการทำเชนแบบ OR คุณต้องรวมพารามิเตอร์เหล่านั้นไว้ใน 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 ซึ่งเกิดขึ้นก่อนที่ระบบจะทราบค่า Flag ข้อจำกัดนี้ถือเป็นข้อจำกัดหลักในการออกแบบ Bazel ที่ไม่น่าจะมีการเปลี่ยนแปลงในเร็วๆ นี้

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

คำค้นหาและ BigQuery ของ Bazel

Bazel query ทำงานเหนือระยะการโหลดของ Bazel ซึ่งหมายความว่าระบบจะไม่ทราบว่าบรรทัดคำสั่งใดใช้แฟล็กเป้าหมาย เนื่องจากจะไม่มีการประเมินแฟล็กเหล่านั้นจนกระทั่งภายหลังในเวอร์ชันบิลด์ (ในขั้นตอนการวิเคราะห์) จึงไม่สามารถระบุสาขา 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 มีค่า Dependency ของ :my_lib มากเกินไป:

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

ส่วน cquery จะแสดงรายการทรัพยากร Dependency ที่แน่นอน

$ 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({
        "//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",
    }),
)

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

อย่างไรก็ตาม มาโครสามารถส่ง 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() จึงแสดงค่า true เสมอ

เนื่องจากมาโคร (ไม่ใช่กฎ) ตามคำจำกัดความจะประเมิน select() ไม่ได้ การพยายามดำเนินการดังกล่าวจึงมักจะเกิดข้อผิดพลาด ดังนี้

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 ออบเจ็กต์ทั้งหมดยกเว้นข้อยกเว้นจำนวนน้อยมากจะแสดงผลเป็น True โดยอัตโนมัติ

ฉันสามารถอ่าน 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 = {
        "//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 + "> $@",
    )

เหตุใด select() จึงใช้ร่วมกับ bind() ไม่ได้

เนื่องจาก bind() เป็นกฎ WORKSPACE ไม่ใช่กฎ BUILD

กฎ Workspace ไม่มีการกำหนดค่าที่เฉพาะเจาะจง และระบบจะประเมินผลในลักษณะเดียวกันกับกฎ "สร้าง" ดังนั้น select() ใน bind() จึงไม่สามารถประเมินใน Branch ที่เฉพาะเจาะจงได้

แต่คุณควรใช้ alias() ที่มี select() ในแอตทริบิวต์ actual เพื่อทำการระบุรันไทม์ประเภทนี้แทน วิธีนี้ทำงานได้อย่างถูกต้องเนื่องจาก alias() เป็นกฎ "การสร้าง" และมีการประเมินด้วยการกำหนดค่าที่เฉพาะเจาะจง

คุณยังมีจุดเป้าหมาย 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() ที่ไม่เลือกเงื่อนไขที่คาดไว้ ให้ใช้ 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บนแพลตฟอร์มจริงๆ

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