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

รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 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 ที่ "เลือก" ของตนเองตามธงที่ บรรทัดคำสั่ง กล่าวโดยละเอียดคือ 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() ในแอตทริบิวต์ configurable แอตทริบิวต์ ใช้ค่าที่แตกต่างกันได้อย่างมีประสิทธิภาพเมื่อมีการเก็บเงื่อนไขที่แตกต่างกัน

รายการที่ตรงกันต้องชัดเจน หากมีหลายเงื่อนไขตรงกัน ระบบจะระบุเงื่อนไขอย่างใดอย่างหนึ่งต่อไปนี้ * ทุกค่าจะมีค่าเหมือนกัน ตัวอย่างเช่น เมื่อเรียกใช้บน Linux x86 สิ่งนี้จะไม่ไม่ชัดเจน {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} เนื่องจากทั้ง 2 สาขาแก้ไขเป็น "สวัสดี" * values ของ 1 คือชุดขั้นสูงที่เข้มงวดกว่าของผู้อื่น เช่น values = {"cpu": "x86", "compilation_mode": "dbg"} เป็นความเชี่ยวชาญพิเศษของ values = {"cpu": "x86"} ที่เห็นได้ชัด

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

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

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

แอตทริบิวต์บางรายการเปลี่ยนพารามิเตอร์บิลด์สำหรับทรัพยากร Dependency แบบทรานซิทีฟทั้งหมด ภายใต้เป้าหมาย เช่น 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 และทรัพยากร Dependency แบบทรานซิทีฟ select บน tool1 ใช้พารามิเตอร์บิลด์ของ tool1 ซึ่งรวมถึง --cpu=x86

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

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

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

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

แฟล็กในตัว

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

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

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

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

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

ลักษณะการทำงานจะเหมือนกับแฟล็กในตัว โปรดดูที่นี่ สำหรับตัวอย่างที่ใช้ได้จริง

--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 ตรงกันเมื่อไม่มีเงื่อนไขอื่น ที่ตรงกัน

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

การทำเชนแบบ 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"],
    }),
)

ซึ่งทำให้จัดการทรัพยากร Dependency ได้ง่ายขึ้น แต่ก็ยังก่อให้เกิดความไม่จำเป็น ข้อมูลที่ซ้ำกัน

โปรดใช้ตัวเลือกต่อไปนี้เพื่อรับการสนับสนุนโดยตรงเพิ่มเติม

selects.with_or

with_or ใน Skylib selects โมดูลรองรับ ORing เงื่อนไขโดยตรงภายใน 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 โมดูลรองรับ ORing 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 แอตทริบิวต์ต่างๆ

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

และการทำสายโซ่

หากต้องการให้ Branch ของ 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โดยตรงไม่ได้ ซึ่งต่างจากการทำเชนแบบ OR ภายใน 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"}, ...)

โดยมีเหตุผล 2 ข้อ

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

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

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

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

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 จะแสดงการขึ้นต่อกันที่แน่นอน:

$ 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()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({
        "//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() อย่างเช่นคำสั่งได้ไหม

มาโครไม่สามารถประเมินรายการที่เลือกได้เนื่องจากมาโครประเมินก่อน 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() ซึ่งเลิกใช้งานแล้วเพื่อใช้ alias() แทน

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

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