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

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

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

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

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

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

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

แพลตฟอร์ม

แม้ว่าความสามารถในการระบุ 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 ที่เป็นเทมเพลตเมื่อคุณจำเป็นต้องตรวจสอบกับค่าเดี่ยวเท่านั้น

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

การรวม 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"],
    }),
)

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

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

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 ในข้อบังคับของ selects ของ Skylib รองรับORconfig_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

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

ซึ่งแตกต่างจากการต่อ OR คือ config_setting ที่มีอยู่จะไม่สามารถ AND โดยตรงภายใน select คุณต้องใส่ config_setting_group ไว้อย่างชัดเจน

ข้อความแสดงข้อผิดพลาดที่กําหนดเอง

โดยค่าเริ่มต้นเมื่อไม่มีเงื่อนไขที่ตรงกัน ระบบจะแนบ select() กับเป้าหมาย ล้มเหลวโดยมีข้อผิดพลาด:

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

ซึ่งจะปรับแต่งได้ด้วยno_match_error แอตทริบิวต์:

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

ความเข้ากันได้ของกฎ

การใช้งานกฎจะได้รับค่าที่แก้ไขแล้วของแอตทริบิวต์ที่กำหนดค่าได้ เช่น

# myapp/BUILD

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

โค้ดการใช้กฎจะเห็น ctx.attr.some_attr เป็น [":foo"]

มาโครสามารถยอมรับวลี select() และส่งต่อไปยังเนทีฟ กฎ แต่ไม่สามารถจัดการกับบุคคลนั้นโดยตรง ตัวอย่างเช่น ไม่มีทางได้ เพื่อให้มาโครแปลง

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

ถึง

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

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

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

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

การค้นหา Bazel และ cquery

Bazel query ทำงานในระยะการโหลดของ Bazel ซึ่งหมายความว่าจะไม่ทราบว่าเป้าหมายใช้ Flag บรรทัดคำสั่งใด เนื่องจากระบบจะไม่ประเมิน Flag เหล่านั้นจนกว่าจะถึงช่วงหลังของการสร้าง (ในระยะการวิเคราะห์) จึงไม่สามารถระบุได้ว่าจะเลือก 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 จะแสดงรายการทรัพยากร 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",
        "//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({
        "//tools/target_cpu:x86": "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 อ่าน Flag บรรทัดคำสั่งของบิลด์ ซึ่งหมายความว่ามีข้อมูลไม่เพียงพอที่จะประเมิน select()

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

# 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({
        "//tools/target_cpu:x86": 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 = {
        "//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

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

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

คุณยังกำหนดให้ bind() ชี้ไปยัง alias() ได้ด้วยหากต้องการ

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

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

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

การตั้งค่านี้ช่วยให้คุณส่ง --define ssl_library=alternative ได้ และเป้าหมายใดก็ตามที่ขึ้นอยู่กับ //:ssl หรือ //external:ssl จะดูทางเลือกได้ที่ @alternative//:ssl

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

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