แอตทริบิวต์ที่กำหนดค่าได้ หรือที่เรียกกันทั่วไปว่า 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's เองก็กำหนดค่าไม่ได้
select() และการขึ้นต่อกัน
แอตทริบิวต์บางอย่างจะเปลี่ยนพารามิเตอร์การสร้างสำหรับการขึ้นต่อกันแบบถ่ายทอดทั้งหมดภายใต้เป้าหมาย เช่น 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 รองรับ ลักษณะการทำงานแบบหลายแพลตฟอร์ม
แฟล็กแบบมีมาในตัว
แฟล็ก เช่น --cpu มีมาในตัว Bazel ซึ่งเครื่องมือการสร้างเข้าใจแฟล็กเหล่านี้โดยกำเนิดสำหรับการสร้างทั้งหมดในทุกโปรเจ็กต์ โดยระบุแฟล็กเหล่านี้ด้วย
config_setting's
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 optvalues = { "force_pic": "true" }จะตรงกับbazel build --force_pic=1values = { "force_pic": "0" }จะตรงกับbazel build --noforce_pic
config_setting รองรับเฉพาะแฟล็กที่ส่งผลต่อลักษณะการทำงานของเป้าหมาย เช่น
--show_progress ไม่อนุญาตให้ใช้ เนื่องจาก
ส่งผลเฉพาะวิธีที่ Bazel รายงานความคืบหน้าต่อผู้ใช้ เป้าหมายจึงใช้แฟล็กดังกล่าวเพื่อสร้างผลลัพธ์ไม่ได้ ระบบไม่ได้บันทึกชุดแฟล็กที่รองรับไว้อย่างชัดเจน แต่ในทางปฏิบัติ แฟล็กส่วนใหญ่ที่ "สมเหตุสมผล" จะทำงานได้
แฟล็กที่กำหนดเอง
คุณสามารถจำลองแฟล็กที่เฉพาะเจาะจงของโปรเจ็กต์ได้ด้วย การตั้งค่าการสร้าง Starlark แฟล็กเหล่านี้จะกำหนดเป็นเป้าหมายการสร้าง ดังนั้น Bazel จึงอ้างอิงแฟล็กเหล่านี้ด้วยป้ายกำกับเป้าหมาย ซึ่งแตกต่างจากแฟล็กแบบมีมาในตัว
แฟล็กเหล่านี้จะทริกเกอร์ด้วยแอตทริบิวต์ config_setting's
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 แอตทริบิวต์ได้
แพลตฟอร์ม
แม้ว่าความสามารถในการระบุแฟล็กหลายรายการในบรรทัดคำสั่งจะมีความยืดหยุ่น แต่การตั้งค่าแต่ละรายการทุกครั้งที่ต้องการสร้างเป้าหมายก็อาจเป็นเรื่องที่น่าเบื่อได้ แพลตฟอร์ม ช่วยให้คุณรวมแฟล็กเหล่านี้ไว้ในชุดง่ายๆ ได้
# 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
chaining
การเชื่อมโยงแบบ OR
ลองพิจารณาสิ่งต่อไปนี้
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": [":standard_lib"],
":config2": [":standard_lib"],
":config3": [":standard_lib"],
":config4": [":special_lib"],
}),
)
เงื่อนไขส่วนใหญ่จะประเมินเป็นการขึ้นต่อกันเดียวกัน แต่ไวยากรณ์นี้อ่านและดูแลรักษายาก จึงควรหลีกเลี่ยงการทำซ้ำ [":standard_lib"] หลาย
ครั้ง
ตัวเลือกหนึ่งคือการกำหนดค่าล่วงหน้าเป็นตัวแปร BUILD ดังนี้
STANDARD_DEP = [":standard_lib"]
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": STANDARD_DEP,
":config2": STANDARD_DEP,
":config3": STANDARD_DEP,
":config4": [":special_lib"],
}),
)
ซึ่งจะช่วยให้จัดการทรัพยากร Dependency ได้ง่ายขึ้น แต่ก็ยังทำให้เกิดการทำซ้ำที่ไม่จำเป็น
หากต้องการรับการสนับสนุนโดยตรงมากขึ้น ให้ใช้อย่างใดอย่างหนึ่งต่อไปนี้
selects.with_or
มาโคร
with_or
ในโมดูล Skylib's
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
ในโมดูล Skylib's
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"],
}),
)
: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"],
}),
)
การเชื่อมโยงแบบ AND จะแตกต่างจากการเชื่อมโยงแบบ OR ตรงที่ config_setting ที่มีอยู่ไม่สามารถ AND ภายใน select ได้โดยตรง คุณต้องห่อหุ้ม `config_setting` เหล่านั้นไว้ใน 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 ที่ไม่น่าจะเปลี่ยนแปลงในเร็วๆ นี้
ประการที่สอง มาโครที่เพียงแค่ต้องวนซ้ำเส้นทาง select ทั้งหมด แม้ว่าในทางเทคนิคจะทำได้ แต่ก็ไม่มี UI ที่สอดคล้องกัน จึงต้องมีการออกแบบเพิ่มเติมเพื่อเปลี่ยนแปลงลักษณะการทำงานนี้
การค้นหาและการค้นหาการกำหนดค่าของ Bazel
Bazel query ทำงานในระยะการโหลด
ของ Bazel
ซึ่งหมายความว่าไม่ทราบว่าเป้าหมายใช้แฟล็กบรรทัดคำสั่งใด เนื่องจาก
แฟล็กเหล่านั้นจะไม่ได้รับการประเมินจนกว่าจะถึงภายหลังในการสร้าง (ใน
ระยะการวิเคราะห์)
`query` จึงกำหนดไม่ได้ว่ามีการเลือกสาขา 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 จะประมาณการขึ้นต่อกันของ :my_lib มากเกินไป
$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep
ขณะที่ cquery จะแสดงการขึ้นต่อกันที่แน่นอนดังนี้
$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep
คำถามที่พบบ่อย
เหตุใด select() จึงใช้ไม่ได้ในมาโคร
select() ใช้ได้ ในกฎ ดูรายละเอียดได้ที่ความเข้ากันได้ของกฎ
โดยปกติแล้วคำถามนี้หมายถึงปัญหาหลักที่ว่า select() ใช้ไม่ได้ใน มาโคร ซึ่งแตกต่างจาก กฎ ดูเอกสารประกอบเกี่ยวกับ กฎและมาโคร เพื่อทำความเข้าใจความแตกต่าง ต่อไปนี้เป็นตัวอย่างแบบครบวงจร
กำหนดกฎและมาโคร
# myapp/defs.bzl
# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
name = ctx.attr.name
allcaps = ctx.attr.my_config_string.upper() # This works fine on all values.
print("My name is " + name + " with custom message: " + allcaps)
# Rule declaration:
my_custom_bazel_rule = rule(
implementation = _impl,
attrs = {"my_config_string": attr.string()},
)
# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
allcaps = my_config_string.upper() # This line won't work with select(s).
print("My name is " + name + " with custom message: " + allcaps)
สร้างอินสแตนซ์กฎและมาโคร
# myapp/BUILD
load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")
my_custom_bazel_rule(
name = "happy_rule",
my_config_string = select({
"//third_party/bazel_platforms/cpu:x86_32": "first string",
"//third_party/bazel_platforms/cpu:ppc": "second string",
}),
)
my_custom_bazel_macro(
name = "happy_macro",
my_config_string = "fixed string",
)
my_custom_bazel_macro(
name = "sad_macro",
my_config_string = select({
"//third_party/bazel_platforms/cpu:x86_32": "first string",
"//third_party/bazel_platforms/cpu:ppc": "other string",
}),
)
การสร้างจะล้มเหลวเนื่องจาก sad_macro ประมวลผล select() ไม่ได้
$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.
การสร้างจะสำเร็จเมื่อคุณแสดงความคิดเห็น sad_macro
# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.
การเปลี่ยนแปลงลักษณะการทำงานนี้เป็นไปไม่ได้เนื่องจาก ตามคำจำกัดความ ระบบจะประเมินมาโครก่อนที่ Bazel จะอ่านแฟล็กบรรทัดคำสั่งของการสร้าง ซึ่งหมายความว่ามีข้อมูลไม่เพียงพอที่จะประเมิน select()
อย่างไรก็ตาม มาโครสามารถส่ง 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()s ได้ การพยายามประเมินจึงมักจะทำให้เกิดข้อผิดพลาดดังนี้
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() เหมือนกับพจนานุกรมได้ไหม
มาโคร ไม่สามารถประเมิน 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() เนื่องจากระบบเลิกใช้งาน `bind()` แล้วและแนะนำให้ใช้ alias() แทน
คำตอบทางเทคนิคคือ bind() เป็นกฎของที่เก็บ
ไม่ใช่กฎ 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 ไม่แนะนำให้ทำเช่นนี้ เนื่องจากจะจำกัดบิลด์ของคุณมากเกินไปและทำให้ผู้ใช้สับสนเมื่อเงื่อนไขที่คาดไว้ไม่ตรงกัน