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

รายงานปัญหา ดูแหล่งที่มา

แอตทริบิวต์ที่กำหนดค่าได้ หรือที่รู้จักกันโดยทั่วไปว่า 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"} โดยไม่มีความกำกวม เนื่องจากทั้งสอง Branch จะเปลี่ยนเป็น "hello" * values ของ One คือ Superset ที่เข้มงวดของคนอื่นๆ ทั้งหมด เช่น values = {"cpu": "x86", "compilation_mode": "dbg"} เป็นความเชี่ยวชาญพิเศษของ values = {"cpu": "x86"} ที่ไม่ชัดเจน

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

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

select() และการอ้างอิง

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

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

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

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

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

แฟล็กในตัว

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

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 จึงอ้างอิงรายการเหล่านี้ด้วยป้ายกำกับเป้าหมาย

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

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

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

--define เป็นไวยากรณ์ทางเลือกแบบเดิมสำหรับแฟล็กที่กำหนดเอง (เช่น --define foo=bar) ค่านี้อาจแสดงในแอตทริบิวต์ค่า (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

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

แพลตฟอร์ม

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

# 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 ตรงกันเมื่อตรงกับเงื่อนไขหลายรายการ ให้พิจารณาและทำการเชน

การผูก 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 ในโมดูล 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 ในโมดูล selects ของ Skylib ที่รองรับ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 ซึ่งเกิดขึ้นก่อนที่จะรู้ค่าแฟล็ก นี่คือข้อจำกัดหลักของการออกแบบของ Bazel ที่มีแนวโน้มว่าจะไม่เปลี่ยนแปลงในเร็วๆ นี้

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

คำค้นหาและ cquery ของ 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({
        "//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() ได้ ความพยายามใดๆ ในการดำเนินการดังกล่าวจะก่อให้เกิดข้อผิดพลาด

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() เป็นกฎที่เก็บ ไม่ใช่กฎการสร้าง

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

คุณควรใช้ alias() ที่มี select() ในแอตทริบิวต์ actual เพื่อระบุรันไทม์ประเภทนี้แทน ซึ่งทํางานได้อย่างถูกต้อง เนื่องจาก 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 เพื่อเข้าถึงเอกสารที่ต้องการ

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