แอตทริบิวต์ที่กำหนดค่าได้ หรือที่เรียกกันโดยทั่วไปว่า select()
เป็นฟีเจอร์ของ Bazel ที่ช่วยให้ผู้ใช้สลับค่า
ของแอตทริบิวต์กฎการสร้างที่บรรทัดคำสั่งได้
ซึ่งสามารถใช้ได้ เช่น สำหรับไลบรารีแบบหลายแพลตฟอร์มที่เลือกการใช้งานที่เหมาะสมกับสถาปัตยกรรมโดยอัตโนมัติ หรือสำหรับไบนารีที่กำหนดค่าฟีเจอร์ได้ซึ่งปรับแต่งได้ในเวลาบิลด์
ตัวอย่าง
# myapp/BUILD
cc_binary(
name = "mybinary",
srcs = ["main.cc"],
deps = select({
":arm_build": [":arm_lib"],
":x86_debug_build": [":x86_dev_lib"],
"//conditions:default": [":generic_lib"],
}),
)
config_setting(
name = "arm_build",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_debug_build",
values = {
"cpu": "x86",
"compilation_mode": "dbg",
},
)
ซึ่งจะประกาศ cc_binary
ที่ "เลือก" การขึ้นต่อกันตามแฟล็กที่
บรรทัดคำสั่ง กล่าวโดยละเอียดคือ deps
จะกลายเป็น
คำสั่ง | deps = |
bazel build //myapp:mybinary --cpu=arm |
[":arm_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=x86 |
[":x86_dev_lib"] |
bazel build //myapp:mybinary --cpu=ppc |
[":generic_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=ppc |
[":generic_lib"] |
select()
เป็นตัวยึดตำแหน่งสำหรับค่าที่จะเลือกตาม
เงื่อนไขการกำหนดค่า ซึ่งเป็นป้ายกำกับที่อ้างอิงถึงเป้าหมาย config_setting
การใช้ select()
ในแอตทริบิวต์ที่กำหนดค่าได้จะทำให้แอตทริบิวต์
ใช้ค่าที่แตกต่างกันอย่างมีประสิทธิภาพเมื่อมีเงื่อนไขที่แตกต่างกัน
การจับคู่ต้องชัดเจน หากมีหลายเงื่อนไขที่ตรงกัน เงื่อนไขทั้งหมดต้อง
* ให้ค่าเดียวกัน เช่น เมื่อเรียกใช้ใน Linux x86 จะไม่มีความกำกวม
{"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}
เนื่องจากทั้ง 2 สาขาจะแสดงผลเป็น "hello"
* values
ของคนหนึ่งเป็นซูเปอร์เซ็ตแท้ของคนอื่นๆ ทั้งหมด เช่น values = {"cpu": "x86", "compilation_mode": "dbg"}
เป็นเวอร์ชันเฉพาะของ values = {"cpu": "x86"}
ที่ไม่คลุมเครือ
เงื่อนไขในตัว //conditions:default
จะจับคู่โดยอัตโนมัติเมื่อ
ไม่มีเงื่อนไขอื่น
แม้ว่าตัวอย่างนี้จะใช้ deps
แต่ select()
ก็ใช้ได้ดีเช่นกันใน srcs
, resources
, cmd
และแอตทริบิวต์อื่นๆ ส่วนใหญ่ มีแอตทริบิวต์เพียงไม่กี่รายการ
ที่กำหนดค่าไม่ได้ และเราจะใส่คำอธิบายประกอบไว้อย่างชัดเจน เช่น แอตทริบิวต์ values
config_setting
ของตัวเอง
ไม่สามารถกำหนดค่าได้
select()
และทรัพยากร Dependency
แอตทริบิวต์บางอย่างจะเปลี่ยนพารามิเตอร์บิลด์สําหรับการขึ้นต่อกันแบบทรานซิทีฟทั้งหมด
ภายใต้เป้าหมาย เช่น genrule
tools
เปลี่ยนแปลง --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
on
tool1
ใช้พารามิเตอร์การสร้างของ tool1
ซึ่งรวมถึง --cpu=x86
เงื่อนไขการกำหนดค่า
คีย์แต่ละรายการในแอตทริบิวต์ที่กำหนดค่าได้คือการอ้างอิงป้ายกำกับไปยัง
config_setting
หรือ
constraint_value
config_setting
เป็นเพียงชุดของ
การตั้งค่าแฟล็กบรรทัดคำสั่งที่คาดไว้ การห่อหุ้มเงื่อนไขเหล่านี้ไว้ในเป้าหมายทำให้ง่ายต่อการรักษาเงื่อนไข "มาตรฐาน" ที่ผู้ใช้สามารถอ้างอิงได้จากหลายที่
constraint_value
รองรับพฤติกรรมแบบหลายแพลตฟอร์ม
ฟีเจอร์ที่สร้างไว้ล่วงหน้า
Bazel มีฟีเจอร์ต่างๆ เช่น --cpu
อยู่ในตัว ซึ่งเป็นเครื่องมือบิลด์ที่เข้าใจฟีเจอร์เหล่านี้โดยกำเนิด
สำหรับการบิลด์ทั้งหมดในทุกโปรเจ็กต์ โดยระบุด้วยแอตทริบิวต์
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 รายงานความคืบหน้าต่อผู้ใช้เท่านั้น เป้าหมายไม่สามารถใช้แฟล็กนั้นเพื่อสร้างผลลัพธ์ได้ ระบบไม่ได้บันทึกชุดแฟล็กที่รองรับที่แน่นอน ในทางปฏิบัติแล้ว แฟล็กส่วนใหญ่ที่ "สมเหตุสมผล" จะใช้งานได้
เครื่องหมายระบุที่กำหนดเอง
คุณสามารถสร้างรูปแบบแฟล็กเฉพาะโปรเจ็กต์ของคุณเองได้ด้วยการตั้งค่าบิลด์ Starlark ซึ่งต่างจากฟีเจอร์ค่าสถานะในตัวตรงที่กำหนดเป็นเป้าหมายการสร้าง ดังนั้น Bazel จึงอ้างอิงฟีเจอร์ค่าสถานะเหล่านี้ด้วยป้ายกำกับเป้าหมาย
โดยจะทริกเกอร์ด้วยแอตทริบิวต์ config_setting
flag_values
ดังนี้
config_setting(
name = "meaningful_condition_name",
flag_values = {
"//myflags:flag1": "value1",
"//myflags:flag2": "value2",
...
},
)
ลักษณะการทำงานจะเหมือนกับการแจ้งที่ไม่เหมาะสมในตัว ดูตัวอย่างที่ใช้งานได้ที่นี่
--define
เป็นไวยากรณ์เดิมอีกแบบสำหรับค่าสถานะที่กำหนดเอง (เช่น
--define foo=bar
) ซึ่งแสดงได้ทั้งในแอตทริบิวต์
values
(values = {"define": "foo=bar"}
) หรือแอตทริบิวต์
define_values
(define_values = {"foo": "bar"}
) --define
รองรับเฉพาะความเข้ากันได้แบบย้อนกลับ
เลือกใช้การตั้งค่าการสร้าง Starlark ทุกครั้งที่ทำได้
values
, flag_values
และ define_values
ประเมินแยกกัน config_setting
จะตรงกันหากค่าทั้งหมดในทุกรายการตรงกัน
เงื่อนไขเริ่มต้น
เงื่อนไขในตัว //conditions:default
จะตรงกันเมื่อไม่มีเงื่อนไขอื่น
ตรงกัน
เนื่องจากกฎ "ตรงกัน 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
หากต้องการให้ข้อผิดพลาดชัดเจนยิ่งขึ้น คุณสามารถตั้งค่าข้อความที่กำหนดเองด้วยแอตทริบิวต์ select()
's
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
ตรงกันเมื่อมีหลายเงื่อนไขตรงกัน ให้พิจารณาการเชื่อมโยง 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
ในโมดูล Skylib
selects
รองรับเงื่อนไข OR
โดยตรงภายใน select
ดังนี้
load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = selects.with_or({
(":config1", ":config2", ":config3"): [":standard_lib"],
":config4": [":special_lib"],
}),
)
selects.config_setting_group
มาโคร
config_setting_group
ในโมดูล 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"],
}),
)
selects.with_or
ต่างจาก :config1_or_2
ตรงที่เป้าหมายต่างๆ สามารถแชร์ในแอตทริบิวต์ต่างๆ ได้
การที่เงื่อนไขหลายรายการตรงกันถือเป็นข้อผิดพลาด เว้นแต่เงื่อนไขหนึ่งจะเป็น "การเจาะจง" ที่ชัดเจนของเงื่อนไขอื่นๆ หรือเงื่อนไขทั้งหมดเปลี่ยนเป็นค่าเดียวกัน ดูรายละเอียดได้ที่นี่
การเชื่อมโยง 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 ที่สอดคล้องกัน คุณต้องออกแบบเพิ่มเติมเพื่อเปลี่ยน
สิ่งนี้
การค้นหา Bazel และ cquery
Bazel query
จะทำงานในระยะการโหลดของ Bazel
ซึ่งหมายความว่าไม่ทราบว่าเป้าหมายใช้ Flag บรรทัดคำสั่งใด เนื่องจากระบบจะไม่ประเมิน Flag เหล่านั้นจนกว่าจะถึงช่วงท้ายๆ ของบิลด์ (ในระยะการวิเคราะห์)
จึงไม่สามารถระบุได้ว่าระบบเลือกสาขา 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",
"//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 จะอ่านแฟล็กบรรทัดคำสั่งของบิลด์ ซึ่งหมายความว่ามีข้อมูลไม่เพียงพอที่จะประเมิน 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() เหมือนดิกชันนารีได้ไหม
มาโครไม่สามารถประเมิน select(s) ได้เนื่องจากมาโครจะประเมินก่อนที่
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()
โดยเราเลิกใช้งานแล้วเพื่อใช้ alias()
แทน
คำตอบทางเทคนิคคือ bind()
เป็นกฎ repo
ไม่ใช่กฎ BUILD
กฎของที่เก็บไม่มีการกำหนดค่าที่เฉพาะเจาะจง และไม่ได้ประเมินในลักษณะเดียวกับกฎ BUILD
ดังนั้น select()
ใน bind()
จึงไม่สามารถ
ประเมินเป็นกิ่งก้านที่เฉพาะเจาะจงได้
แต่คุณควรใช้ alias()
โดยมี select()
ในแอตทริบิวต์ actual
เพื่อทำการกำหนดประเภทนี้ในรันไทม์ ซึ่งจะทำงานได้อย่างถูกต้องเนื่องจาก alias()
เป็นกฎ BUILD และได้รับการประเมินด้วยการกำหนดค่าที่เฉพาะเจาะจง
$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)
$ cat BUILD
config_setting(
name = "alt_ssl",
define_values = {
"ssl_library": "alternative",
},
)
alias(
name = "ssl",
actual = select({
"//:alt_ssl": "@alternative//:ssl",
"//conditions:default": "@boringssl//:ssl",
}),
)
เมื่อตั้งค่านี้แล้ว คุณจะส่ง --define ssl_library=alternative
ได้ และเป้าหมายใดก็ตามที่ขึ้นอยู่กับ //:ssl
หรือ //external:ssl
จะเห็นทางเลือกที่อยู่ใน @alternative//:ssl
แต่จริงๆ แล้วคุณควรหยุดใช้ bind()
ทำไม select() จึงไม่เลือกสิ่งที่ฉันคาดหวัง
หาก //myapp:foo
มี select()
ที่ไม่ได้เลือกเงื่อนไขที่คุณคาดไว้
ให้ใช้ cquery และ bazel config
เพื่อแก้ไขข้อบกพร่อง
หาก //myapp:foo
เป็นเป้าหมายระดับบนสุดที่คุณกำลังสร้าง ให้เรียกใช้คำสั่งต่อไปนี้
$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)
หากคุณกำลังสร้างเป้าหมาย //bar
อื่นที่ขึ้นอยู่กับ
//myapp:foo ที่ใดที่หนึ่งในกราฟย่อย ให้เรียกใช้คำสั่งต่อไปนี้
$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)
(12e23b9a2b534a)
ข้าง //myapp:foo
คือแฮชของการกำหนดค่าที่แสดงผล select()
ของ //myapp:foo
คุณตรวจสอบค่าของตัวแปรได้ด้วย bazel config
ดังนี้
$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
cpu: darwin
compilation_mode: fastbuild
...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
linkopt: [-Dfoo=bar]
...
}
...
จากนั้นเปรียบเทียบเอาต์พุตนี้กับการตั้งค่าที่คาดหวังของแต่ละ config_setting
//myapp:foo
อาจมีการกำหนดค่าที่แตกต่างกันในบิลด์เดียวกัน ดูคำแนะนำในการใช้ somepath
เพื่อรับ
เอกสารประกอบ cquery ที่ถูกต้อง
เหตุใด select()
จึงใช้กับแพลตฟอร์มไม่ได้
Bazel ไม่รองรับการตรวจสอบแอตทริบิวต์ที่กำหนดค่าได้ว่าแพลตฟอร์มที่ระบุเป็นแพลตฟอร์มเป้าหมายหรือไม่ เนื่องจากความหมายไม่ชัดเจน
เช่น
platform(
name = "x86_linux_platform",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
ในไฟล์ BUILD
นี้ ควรใช้ select()
ใดหากแพลตฟอร์มเป้าหมายมีทั้งข้อจำกัด @platforms//cpu:x86
และ @platforms//os:linux
แต่ไม่ใช่ :x86_linux_platform
ที่กำหนดไว้ที่นี่ ผู้เขียนไฟล์ BUILD
และผู้ใช้
ที่กำหนดแพลตฟอร์มแยกต่างหากอาจมีความคิดเห็นที่แตกต่างกัน
ฉันควรทำอย่างไรแทน
ให้กำหนด config_setting
ที่ตรงกับแพลตฟอร์มใดก็ได้ที่มีข้อจำกัดต่อไปนี้แทน
config_setting(
name = "is_x86_linux",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_x86_linux": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
กระบวนการนี้กำหนดความหมายที่เฉพาะเจาะจง ซึ่งจะช่วยให้ผู้ใช้เข้าใจได้ชัดเจนยิ่งขึ้นว่าแพลตฟอร์มใดตรงตามเงื่อนไขที่ต้องการ
ฉันควรทำอย่างไรหากต้องการselect
ในแพลตฟอร์มจริงๆ
หากข้อกำหนดในการสร้างระบุให้ตรวจสอบแพลตฟอร์ม คุณ
สามารถเปลี่ยนค่าของแฟล็ก --platforms
ใน config_setting
ได้โดยทำดังนี้
config_setting(
name = "is_specific_x86_linux_platform",
values = {
"platforms": ["//package:x86_linux_platform"],
},
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
ทีม Bazel ไม่แนะนำให้ทำเช่นนี้ เนื่องจากจะจำกัดการบิลด์มากเกินไปและ ทำให้ผู้ใช้สับสนเมื่อเงื่อนไขที่คาดไว้ไม่ตรงกัน