แอตทริบิวต์ที่กำหนดค่าได้ หรือที่เรียกกันทั่วไปว่า 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 ตาม Flag ในบรรทัดคำสั่ง กล่าวโดยละเอียดคือ deps
จะกลายเป็น
คำสั่ง | deps = |
bazel build //myapp:mybinary --cpu=arm |
[":arm_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=x86 |
[":x86_dev_lib"] |
bazel build //myapp:mybinary --cpu=ppc |
[":generic_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=ppc |
[":generic_lib"] |
select()
ทำหน้าที่เป็นตัวยึดตำแหน่งสำหรับค่าที่จะเลือกตามเงื่อนไขการกำหนดค่า ซึ่งเป็นป้ายกำกับที่อ้างอิงเป้าหมาย config_setting
การใช้ select()
ในแอตทริบิวต์ที่กำหนดค่าได้จะทำให้แอตทริบิวต์ใช้ค่าต่างๆ ได้อย่างมีประสิทธิภาพเมื่อเงื่อนไขต่างๆ ตรงกัน
รายการที่ตรงกันต้องชัดเจน หากมีหลายเงื่อนไขที่ตรงกัน
* ทุกเงื่อนไขจะตรงกันเป็นค่าเดียวกัน ตัวอย่างเช่น เมื่อเรียกใช้บน Linux x86 ค่า {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}
จะไม่ชัดเจนเนื่องจากทั้ง 2 สาขาแก้ไขเป็น "hello"
* values
ของ 1 คือชุดขั้นสูงที่เข้มงวดกว่าของผู้อื่น เช่น values = {"cpu": "x86", "compilation_mode": "dbg"}
เป็นสาขาเฉพาะของ values = {"cpu": "x86"}
ที่ชัดเจน
เงื่อนไขในตัว //conditions:default
จะจับคู่โดยอัตโนมัติเมื่อไม่มีเงื่อนไขอื่นใดจับคู่
แม้ว่าตัวอย่างนี้จะใช้ deps
แต่ select()
ก็ใช้ได้กับ srcs
, resources
, cmd
และแอตทริบิวต์อื่นๆ ส่วนใหญ่เช่นกัน มีแอตทริบิวต์เพียงไม่กี่รายการที่กำหนดค่าไม่ได้และมีการใส่คำอธิบายประกอบไว้อย่างชัดเจน ตัวอย่างเช่น คุณจะกำหนดค่าแอตทริบิวต์ values
ของ config_setting
ไม่ได้
select()
และทรัพยากร Dependency
แอตทริบิวต์บางอย่างจะเปลี่ยนพารามิเตอร์การสร้างสําหรับทรัพยากร transitive ทั้งหมดภายใต้เป้าหมาย เช่น tools
ของ genrule
จะเปลี่ยน --cpu
เป็น CPU ของเครื่องที่ใช้ Bazel (ซึ่งอาจแตกต่างจาก CPU ที่คอมไพล์เป้าหมายด้วยเหตุผลของการคอมไพล์ข้าม) ซึ่งเรียกว่าการเปลี่ยนการกำหนดค่า
ระบุ
#myapp/BUILD
config_setting(
name = "arm_cpu",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
genrule(
name = "my_genrule",
srcs = select({
":arm_cpu": ["g_arm.src"],
":x86_cpu": ["g_x86.src"],
}),
tools = select({
":arm_cpu": [":tool1"],
":x86_cpu": [":tool2"],
}),
)
cc_binary(
name = "tool1",
srcs = select({
":arm_cpu": ["armtool.cc"],
":x86_cpu": ["x86tool.cc"],
}),
)
วิ่ง
$ bazel build //myapp:my_genrule --cpu=arm
ในเครื่องของนักพัฒนาซอฟต์แวร์ x86
จะเชื่อมโยงบิลด์กับ g_arm.src
, tool1
และ x86tool.cc
select
ทั้ง 2 รายการที่แนบมากับ my_genrule
ใช้พารามิเตอร์การสร้างของ my_genrule
ซึ่งรวมถึง --cpu=arm
แอตทริบิวต์ tools
เปลี่ยนจาก --cpu
เป็น x86
สำหรับ tool1
และความเกี่ยวข้องแบบเปลี่ยนผ่าน select
ใน
tool1
ใช้พารามิเตอร์การสร้างของ tool1
ซึ่งรวมถึง --cpu=x86
เงื่อนไขการกำหนดค่า
คีย์แต่ละรายการในแอตทริบิวต์ที่กำหนดค่าได้คือป้ายกำกับที่อ้างอิงถึง config_setting
หรือ constraint_value
config_setting
เป็นเพียงชุดการตั้งค่า Flag บรรทัดคำสั่งที่คาดไว้ การรวมเงื่อนไขเหล่านี้ไว้ในเป้าหมายจะช่วยให้คุณดูแลรักษาเงื่อนไข "มาตรฐาน" ที่ผู้ใช้อ้างอิงได้จากหลายที่
constraint_value
รองรับลักษณะการทำงานแบบหลายแพลตฟอร์ม
ธงในตัว
Flag อย่างเช่น --cpu
สร้างขึ้นใน Bazel: เครื่องมือสร้างจะเข้าใจ Flag เหล่านี้โดยกำเนิดสำหรับบิลด์ทั้งหมดในโปรเจ็กต์ทั้งหมด ค่าเหล่านี้ระบุด้วยแอตทริบิวต์ values
ของ config_setting
config_setting(
name = "meaningful_condition_name",
values = {
"flag1": "value1",
"flag2": "value2",
...
},
)
flagN
คือชื่อ Flag (ไม่มี --
เช่น "cpu"
แทน "--cpu"
) valueN
คือค่าที่คาดไว้สำหรับ Flag นั้น :meaningful_condition_name
จะถือว่าตรงกันหาก
ทุกรายการใน values
ตรงกัน ลําดับไม่เกี่ยวข้อง
ระบบจะแยกวิเคราะห์ valueN
ราวกับมีการตั้งค่าในบรรทัดคำสั่ง ซึ่งหมายความว่า
values = { "compilation_mode": "opt" }
ตรงกับbazel build -c opt
values = { "force_pic": "true" }
ตรงกับbazel build --force_pic=1
values = { "force_pic": "0" }
ตรงกับbazel build --noforce_pic
config_setting
รองรับเฉพาะ Flag ที่ส่งผลต่อลักษณะการทํางานของเป้าหมาย ตัวอย่างเช่น ระบบไม่อนุญาตให้ใช้ --show_progress
เนื่องจากจะส่งผลต่อวิธีรายงานความคืบหน้าของ Bazel ไปยังผู้ใช้เท่านั้น เป้าหมายจะใช้ Flag ดังกล่าวเพื่อสร้างผลลัพธ์ไม่ได้ เราไม่บันทึกชุด Flag ที่รองรับไว้ ในทางปฏิบัติแล้ว การแจ้งว่าไม่เหมาะสมส่วนใหญ่ที่ "สมเหตุสมผล" จะใช้งานได้
เครื่องหมายระบุที่กำหนดเอง
คุณสามารถสร้าง Flag สำหรับโปรเจ็กต์ของคุณเองได้โดยใช้การตั้งค่าการสร้าง Starlark ซึ่งต่างจาก Flag ในตัวตรงที่ Flag เหล่านี้จะกำหนดเป็นเป้าหมายการสร้าง ดังนั้น Bazel จึงอ้างอิง Flag เหล่านี้ด้วยป้ายกำกับเป้าหมาย
เหตุการณ์เหล่านี้จะทริกเกอร์ด้วยแอตทริบิวต์ flag_values
ของ config_setting
config_setting(
name = "meaningful_condition_name",
flag_values = {
"//myflags:flag1": "value1",
"//myflags:flag2": "value2",
...
},
)
ลักษณะการทำงานจะเหมือนกับFlag ในตัว ดูตัวอย่างที่ใช้งานได้ที่นี่
--define
เป็นไวยากรณ์เดิมทางเลือกสำหรับ Flag ที่กําหนดเอง (เช่น --define foo=bar
) ซึ่งสามารถแสดงในแอตทริบิวต์ values (values = {"define": "foo=bar"}
) หรือแอตทริบิวต์ define_values (define_values = {"foo": "bar"}
) ก็ได้ ระบบจะรองรับ --define
เพื่อการรองรับการทำงานร่วมกันแบบย้อนหลังเท่านั้น โปรดใช้การตั้งค่าบิลด์ Starlark ทุกครั้งที่เป็นไปได้
values
, flag_values
และ define_values
จะประเมินแยกกัน config_setting
จะจับคู่หากค่าทั้งหมดในค่าทั้งหมดตรงกัน
เงื่อนไขเริ่มต้น
เงื่อนไขในตัว //conditions:default
จะตรงกันเมื่อไม่มีเงื่อนไขอื่นตรงกัน
เนื่องจากกฎ "การจับคู่ที่ตรงกันเพียงรายการเดียว" แอตทริบิวต์ที่กำหนดค่าได้ซึ่งไม่ตรงกันและไม่มีเงื่อนไขเริ่มต้นจะแสดงข้อผิดพลาด "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
แบบ Boilerplate เมื่อคุณต้องการตรวจสอบค่าเดี่ยวๆ เท่านั้น
แพลตฟอร์มยังอยู่ระหว่างการพัฒนา ดูรายละเอียดได้ในเอกสารประกอบ
การรวม select()
select
ปรากฏได้หลายครั้งในแอตทริบิวต์เดียวกัน
sh_binary(
name = "my_target",
srcs = ["always_include.sh"] +
select({
":armeabi_mode": ["armeabi_src.sh"],
":x86_mode": ["x86_src.sh"],
}) +
select({
":opt_mode": ["opt_extras.sh"],
":dbg_mode": ["dbg_extras.sh"],
}),
)
select
ต้องไม่ปรากฏภายใน select
อื่น หากต้องการฝัง selects
และแอตทริบิวต์ใช้เป้าหมายอื่นเป็นค่า ให้ใช้เป้าหมายตัวกลางดังนี้
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":armeabi_mode": [":armeabi_lib"],
...
}),
)
sh_library(
name = "armeabi_lib",
srcs = select({
":opt_mode": ["armeabi_with_opt.sh"],
...
}),
)
หากต้องการให้ select
จับคู่เมื่อหลายเงื่อนไขตรงกัน ให้พิจารณาการต่อเชื่อม AND
การต่อ OR
ลองพิจารณาสิ่งเหล่านี้
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": [":standard_lib"],
":config2": [":standard_lib"],
":config3": [":standard_lib"],
":config4": [":special_lib"],
}),
)
เงื่อนไขส่วนใหญ่จะประเมินเป็น dep เดียวกัน แต่ไวยากรณ์นี้อ่านและดูแลรักษาได้ยาก คงจะดีหากไม่ต้องป้อน [":standard_lib"]
ซ้ำหลายครั้ง
ตัวเลือกหนึ่งคือกําหนดค่าไว้ล่วงหน้าเป็นตัวแปร BUILD ดังนี้
STANDARD_DEP = [":standard_lib"]
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": STANDARD_DEP,
":config2": STANDARD_DEP,
":config3": STANDARD_DEP,
":config4": [":special_lib"],
}),
)
ซึ่งจะช่วยให้จัดการการพึ่งพาได้ง่ายขึ้น แต่ก็ทำให้เกิดการ ทำซ้ำโดยไม่จำเป็น
หากต้องการการสนับสนุนโดยตรงมากขึ้น ให้ใช้วิธีใดวิธีหนึ่งต่อไปนี้
selects.with_or
มาโคร with_or ในโมดูล selects
ของ Skylib รองรับ OR
ing เงื่อนไขโดยตรงภายใน select
ดังนี้
load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = selects.with_or({
(":config1", ":config2", ":config3"): [":standard_lib"],
":config4": [":special_lib"],
}),
)
selects.config_setting_group
มาโคร
config_setting_group
ในโมดูลของ Skylib
selects
รองรับOR
ใช้ config_setting
หลายตัว ดังนี้
load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_or_2",
match_any = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_or_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
ซึ่งต่างจาก 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 ซึ่งเกิดขึ้นก่อนที่ระบบจะทราบค่า Flag
ข้อจำกัดนี้ถือเป็นข้อจำกัดหลักในการออกแบบ 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()
ตามมาตรฐานการออกแบบแบบ Python ออบเจ็กต์ทั้งหมดยกเว้นข้อยกเว้นจำนวนน้อยมากจะแสดงผลเป็น True โดยอัตโนมัติ
ฉันจะอ่าน select() เหมือนกับพจนานุกรมได้ไหม
มาโครไม่สามารถประเมิน select ได้เนื่องจากมาโครจะประเมินก่อน
Bazel จะรู้พารามิเตอร์บรรทัดคำสั่งของบิลด์ อย่างน้อยก็อ่านพจนานุกรมของ select()
ได้ไหม เช่น เพิ่มส่วนต่อท้ายให้กับแต่ละค่า
แนวคิดนี้เป็นไปได้ แต่ยังไม่ถือเป็นฟีเจอร์ของ Bazel
สิ่งที่คุณทำได้ในวันนี้คือเตรียมพจนานุกรมแบบตรงๆ แล้วป้อนลงใน select()
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
+ " > $@"
)
$ cat myapp/BUILD
selecty_genrule(
name = "selecty",
select_cmd = {
"//tools/target_cpu:x86": "x86 mode",
},
)
$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX
หากต้องการรองรับทั้ง select()
และประเภทเนทีฟ ให้ทำดังนี้
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
cmd_suffix = ""
if type(select_cmd) == "string":
cmd_suffix = select_cmd + " WITH SUFFIX"
elif type(select_cmd) == "dict":
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
cmd_suffix = select(select_cmd + {"//conditions:default": "default"})
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + cmd_suffix + "> $@",
)
เหตุใด select() จึงใช้ร่วมกับ bind() ไม่ได้
ก่อนอื่น อย่าใช้ bind()
นโยบายนี้เลิกใช้งานแล้วเพื่อใช้ 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
บนแพลตฟอร์มจริงๆ
หากข้อกำหนดของบิลด์กำหนดให้ตรวจสอบแพลตฟอร์มโดยเฉพาะ คุณสามารถพลิกค่าของ 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 ไม่สนับสนุนให้ทำเช่นนี้ เนื่องจากจะจำกัดการสร้างมากเกินไปและทำให้ผู้ใช้สับสนเมื่อเงื่อนไขที่คาดไว้ไม่ตรงกัน