แอตทริบิวต์ที่กำหนดค่าได้ หรือที่รู้จักกันโดยทั่วไปว่า select()
เป็นฟีเจอร์ Bazel ที่ให้ผู้ใช้สลับค่าของแอตทริบิวต์กฎการสร้างได้ในบรรทัดคำสั่ง
เช่น สำหรับไลบรารีหลายแพลตฟอร์มที่เลือกการติดตั้งใช้งานที่เหมาะกับสถาปัตยกรรมโดยอัตโนมัติ หรือสำหรับไบนารีที่กำหนดค่าฟีเจอร์ได้ซึ่งปรับแต่งได้ในเวลาบิลด์
ตัวอย่าง
# myapp/BUILD
cc_binary(
name = "mybinary",
srcs = ["main.cc"],
deps = select({
":arm_build": [":arm_lib"],
":x86_debug_build": [":x86_dev_lib"],
"//conditions:default": [":generic_lib"],
}),
)
config_setting(
name = "arm_build",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_debug_build",
values = {
"cpu": "x86",
"compilation_mode": "dbg",
},
)
ซึ่งจะประกาศ cc_binary
ที่ "เลือก" ค่า Dep ตามแฟล็กในบรรทัดคำสั่ง โดย deps
จะกลายเป็น
คำสั่ง | deps = |
bazel build //myapp:mybinary --cpu=arm |
[":arm_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=x86 |
[":x86_dev_lib"] |
bazel build //myapp:mybinary --cpu=ppc |
[":generic_lib"] |
bazel build //myapp:mybinary -c dbg --cpu=ppc |
[":generic_lib"] |
select()
ทำหน้าที่เป็นตัวยึดตำแหน่งสำหรับค่าที่จะเลือกตามเงื่อนไขการกำหนดค่า ซึ่งเป็นป้ายกำกับที่อ้างอิงเป้าหมาย config_setting
การใช้ select()
ในแอตทริบิวต์ที่กำหนดค่าได้จะทำให้แอตทริบิวต์ใช้ค่าที่แตกต่างกันได้อย่างมีประสิทธิภาพเมื่อมีเงื่อนไขที่แตกต่างกัน
การจับคู่ต้องชัดเจน: หากเงื่อนไขหลายข้อตรงกัน ก็หมายความว่าเงื่อนไขทั้งหมดจะมีค่าเดียวกัน เช่น เมื่อเรียกใช้ใน linux x86 เป็น {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}
โดยไม่มีความกำกวม เนื่องจากทั้งสอง Branch จะเปลี่ยนเป็น "hello"
* values
ของ One คือ Superset ที่เข้มงวดของคนอื่นๆ ทั้งหมด เช่น values = {"cpu": "x86", "compilation_mode": "dbg"}
เป็นความเชี่ยวชาญพิเศษของ values = {"cpu": "x86"}
ที่ไม่ชัดเจน
เงื่อนไขในตัว //conditions:default
จะจับคู่โดยอัตโนมัติเมื่อไม่มีอย่างอื่นที่ตรงกัน
แม้ว่าตัวอย่างนี้จะใช้ deps
แต่ select()
ก็ทํางานได้ดีกับ srcs
, resources
, cmd
และแอตทริบิวต์อื่นๆ ส่วนใหญ่ มีแอตทริบิวต์เพียงไม่กี่รายการที่ไม่สามารถกำหนดค่าได้และมีการใส่คำอธิบายประกอบอย่างชัดเจน เช่น แอตทริบิวต์ values
ของ config_setting
กำหนดค่าไม่ได้
select()
และการอ้างอิง
แอตทริบิวต์บางอย่างจะเปลี่ยนพารามิเตอร์บิลด์สำหรับทรัพยากร Dependency ชั่วคราวทั้งหมดภายใต้เป้าหมาย ตัวอย่างเช่น tools
ของ genrule
เปลี่ยน --cpu
เป็น CPU ของเครื่องที่ใช้ Bazel (ซึ่งการคอมไพล์แบบข้ามระบบอาจต่างจาก CPU ที่สร้างเป้าหมายขึ้นมา) วิธีนี้เรียกว่าการเปลี่ยนการกำหนดค่า
ให้
#myapp/BUILD
config_setting(
name = "arm_cpu",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
genrule(
name = "my_genrule",
srcs = select({
":arm_cpu": ["g_arm.src"],
":x86_cpu": ["g_x86.src"],
}),
tools = select({
":arm_cpu": [":tool1"],
":x86_cpu": [":tool2"],
}),
)
cc_binary(
name = "tool1",
srcs = select({
":arm_cpu": ["armtool.cc"],
":x86_cpu": ["x86tool.cc"],
}),
)
กำลังเดิน
$ bazel build //myapp:my_genrule --cpu=arm
ในเครื่องของนักพัฒนาซอฟต์แวร์ x86
จะเชื่อมโยงบิลด์กับ g_arm.src
, tool1
และ x86tool.cc
select
ทั้ง 2 รายการที่แนบกับ my_genrule
ใช้พารามิเตอร์บิลด์ของ my_genrule
ซึ่งรวมถึง --cpu=arm
แอตทริบิวต์ tools
เปลี่ยน --cpu
เป็น x86
สำหรับ tool1
และการอ้างอิงทางอ้อม select
ใน tool1
ใช้พารามิเตอร์บิลด์ของ tool1
ซึ่งรวมถึง --cpu=x86
เงื่อนไขการกำหนดค่า
แต่ละคีย์ในแอตทริบิวต์ที่กำหนดค่าได้คือการอ้างอิงป้ายกำกับไปยัง config_setting
หรือ constraint_value
config_setting
เป็นเพียงคอลเล็กชันของการตั้งค่าแฟล็กบรรทัดคำสั่งที่คาดไว้ การรวมเหตุการณ์เหล่านี้ไว้ในเป้าหมายจะช่วยให้รักษาเงื่อนไข "มาตรฐาน" ซึ่งผู้ใช้อ้างอิงจากหลายๆ แหล่งได้อย่างง่ายดาย
constraint_value
รองรับลักษณะการทำงานในหลายแพลตฟอร์ม
แฟล็กในตัว
แฟล็กอย่าง --cpu
มีอยู่ใน Bazel โดยเครื่องมือสร้างจะเข้าใจแฟล็กนั้นอยู่แล้วสำหรับบิลด์ทั้งหมดในโปรเจ็กต์ทั้งหมด ซึ่งมีการระบุด้วยแอตทริบิวต์ values
ของ config_setting
ดังนี้
config_setting(
name = "meaningful_condition_name",
values = {
"flag1": "value1",
"flag2": "value2",
...
},
)
flagN
เป็นชื่อแฟล็ก (ไม่มี --
ดังนั้นให้ "cpu"
แทน "--cpu"
) valueN
คือค่าที่คาดไว้สำหรับแฟล็กนั้น :meaningful_condition_name
จะจับคู่หาก
ทุกรายการใน values
ตรงกัน ลำดับไม่เกี่ยวข้อง
มีการแยกวิเคราะห์ valueN
เหมือนกับว่าตั้งค่าไว้ในบรรทัดคำสั่ง ซึ่งแปลว่า
values = { "compilation_mode": "opt" }
ตรงกับbazel build -c opt
values = { "force_pic": "true" }
ตรงกับbazel build --force_pic=1
values = { "force_pic": "0" }
ตรงกับbazel build --noforce_pic
config_setting
รองรับเฉพาะแฟล็กที่ส่งผลกระทบต่อพฤติกรรมเป้าหมาย ตัวอย่างเช่น ไม่อนุญาต --show_progress
เนื่องจากมีผลต่อการรายงานความคืบหน้าของผู้ใช้ Bazel เท่านั้น เป้าหมายไม่สามารถใช้การตั้งค่าสถานะ
เพื่อสร้างผลลัพธ์ได้ ไม่มีการบันทึกข้อมูลชุดแฟล็กที่รองรับที่แน่นอน ในทางปฏิบัติ การตั้งค่าสถานะส่วนใหญ่ที่ "สมเหตุสมผล" นั้นใช้ได้ผล
แฟล็กที่กำหนดเอง
คุณจำลองการแฟล็กเฉพาะโปรเจ็กต์ของตัวเองได้ด้วยการตั้งค่าบิลด์ของ Starlark แฟล็กเหล่านี้ต่างจากแฟล็กในตัวตรงที่จะกำหนดเป็นเป้าหมายบิวด์ ดังนั้น Bazel จึงอ้างอิงรายการเหล่านี้ด้วยป้ายกำกับเป้าหมาย
ซึ่งจะมีการเรียกใช้ด้วยแอตทริบิวต์ flag_values
ของ config_setting
ดังนี้
config_setting(
name = "meaningful_condition_name",
flag_values = {
"//myflags:flag1": "value1",
"//myflags:flag2": "value2",
...
},
)
ลักษณะการทำงานจะเหมือนกับแฟล็กในตัว ดูตัวอย่างที่ใช้งานได้ที่นี่
--define
เป็นไวยากรณ์ทางเลือกแบบเดิมสำหรับแฟล็กที่กำหนดเอง (เช่น --define foo=bar
) ค่านี้อาจแสดงในแอตทริบิวต์ค่า (values = {"define": "foo=bar"}
) หรือแอตทริบิวต์define_values
(define_values = {"foo": "bar"}
) ค่า --define
จะรองรับเฉพาะความเข้ากันได้แบบย้อนหลังเท่านั้น เลือกใช้การตั้งค่าบิลด์ของ Starlark ทุกครั้งที่ทำได้
values
, flag_values
และ define_values
จะประเมินแยกกัน config_setting
จะจับคู่หากค่าทั้งหมดตรงกัน
เงื่อนไขเริ่มต้น
เงื่อนไขในตัว //conditions:default
จะจับคู่เมื่อไม่มีเงื่อนไขอื่นๆ ที่ตรงกับเงื่อนไข
เนื่องจากกฎ "ตรงกันทั้งหมด" แอตทริบิวต์ที่กำหนดค่าได้ซึ่งไม่มีรายการที่ตรงกันและไม่มีเงื่อนไขเริ่มต้นจะแสดงข้อผิดพลาด "no matching conditions"
ซึ่งจะป้องกันการทำงานล้มเหลวแบบเงียบจากการตั้งค่าที่ไม่คาดคิด เช่น
# myapp/BUILD
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
cc_library(
name = "x86_only_lib",
srcs = select({
":x86_cpu": ["lib.cc"],
}),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//myapp:x86_cpu
คุณอาจตั้งค่าข้อความที่กำหนดเองด้วยแอตทริบิวต์ no_match_error
ของ select()
เพื่อข้อผิดพลาดที่ชัดเจนยิ่งขึ้น
แพลตฟอร์ม
แม้ว่าความสามารถในการระบุแฟล็กหลายรายการในบรรทัดคำสั่งจะมีความยืดหยุ่น แต่ก็อาจเป็นเรื่องยุ่งยากหากคุณต้องกำหนดแต่ละรายการทุกครั้งที่ต้องการสร้างเป้าหมาย แพลตฟอร์ม ให้คุณรวมรายการเหล่านี้เข้าเป็นชุดง่ายๆ
# myapp/BUILD
sh_binary(
name = "my_rocks",
srcs = select({
":basalt": ["pyroxene.sh"],
":marble": ["calcite.sh"],
"//conditions:default": ["feldspar.sh"],
}),
)
config_setting(
name = "basalt",
constraint_values = [
":black",
":igneous",
],
)
config_setting(
name = "marble",
constraint_values = [
":white",
":metamorphic",
],
)
# constraint_setting acts as an enum type, and constraint_value as an enum value.
constraint_setting(name = "color")
constraint_value(name = "black", constraint_setting = "color")
constraint_value(name = "white", constraint_setting = "color")
constraint_setting(name = "texture")
constraint_value(name = "smooth", constraint_setting = "texture")
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
platform(
name = "basalt_platform",
constraint_values = [
":black",
":igneous",
],
)
platform(
name = "marble_platform",
constraint_values = [
":white",
":smooth",
":metamorphic",
],
)
สามารถระบุแพลตฟอร์มในบรรทัดคำสั่งได้ โดยจะเปิดใช้งาน config_setting
ที่มีชุดย่อยของ constraint_values
ของแพลตฟอร์ม ซึ่งช่วยให้ config_setting
เหล่านั้นจับคู่ในนิพจน์ select()
ได้
ตัวอย่างเช่น ในการตั้งค่าแอตทริบิวต์ srcs
ของ my_rocks
เป็น calcite.sh
คุณสามารถเรียกใช้
bazel build //my_app:my_rocks --platforms=//myapp:marble_platform
หากไม่มีแพลตฟอร์ม อาจมีลักษณะดังนี้
bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic
select()
ยังอ่าน constraint_value
ได้โดยตรงอีกด้วย:
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
name = "my_rocks",
srcs = select({
":igneous": ["igneous.sh"],
":metamorphic" ["metamorphic.sh"],
}),
)
ซึ่งจะช่วยให้ไม่ต้องใช้ config_setting
แบบ Boilerplate เมื่อคุณต้องการตรวจสอบกับค่าเพียงค่าเดียว
แพลตฟอร์มยังอยู่ระหว่างการพัฒนา ดูรายละเอียดในเอกสารประกอบ
การรวม select()
วินาที
select
อาจปรากฏหลายครั้งในแอตทริบิวต์เดียวกัน:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"] +
select({
":armeabi_mode": ["armeabi_src.sh"],
":x86_mode": ["x86_src.sh"],
}) +
select({
":opt_mode": ["opt_extras.sh"],
":dbg_mode": ["dbg_extras.sh"],
}),
)
select
ไม่สามารถแสดงภายใน select
อื่น หากต้องการฝัง selects
และแอตทริบิวต์ใช้เป้าหมายอื่นเป็นค่า ให้ใช้เป้าหมายระดับกลาง ดังนี้
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":armeabi_mode": [":armeabi_lib"],
...
}),
)
sh_library(
name = "armeabi_lib",
srcs = select({
":opt_mode": ["armeabi_with_opt.sh"],
...
}),
)
หากต้องการให้ select
ตรงกันเมื่อตรงกับเงื่อนไขหลายรายการ ให้พิจารณาและทำการเชน
การผูก OR
ลองพิจารณาสิ่งเหล่านี้
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": [":standard_lib"],
":config2": [":standard_lib"],
":config3": [":standard_lib"],
":config4": [":special_lib"],
}),
)
เงื่อนไขส่วนใหญ่จะประเมินได้ที่ระดับเดียวกัน แต่ไวยากรณ์นี้อ่านและดูแลรักษาได้ยาก ถ้าไม่ต้องใส่ [":standard_lib"]
ซ้ำหลายๆ ครั้งก็คงดี
ทางเลือกหนึ่งคือ กำหนดมูลค่าเป็นตัวแปร BUILD ล่วงหน้า ดังนี้
STANDARD_DEP = [":standard_lib"]
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": STANDARD_DEP,
":config2": STANDARD_DEP,
":config3": STANDARD_DEP,
":config4": [":special_lib"],
}),
)
ซึ่งจะช่วยให้จัดการทรัพยากร Dependency ได้ง่ายขึ้น แต่ก็ยังคงก่อให้เกิด ความซ้ำซ้อนที่ไม่จำเป็น
หากต้องการการสนับสนุนโดยตรงเพิ่มเติม โปรดใช้ตัวเลือกใดตัวเลือกหนึ่งต่อไปนี้
selects.with_or
มาโคร with_or ในโมดูล selects
ของ Skylib รองรับOR
เงื่อนไขภายใน select
โดยตรง
load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = selects.with_or({
(":config1", ":config2", ":config3"): [":standard_lib"],
":config4": [":special_lib"],
}),
)
selects.config_setting_group
มาโคร config_setting_group ในโมดูล selects
ของ Skylib ที่รองรับOR
การใช้ config_setting
หลายรายการ ดังนี้
load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_or_2",
match_any = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_or_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
เป้าหมายที่ต่างกันจะใช้ :config1_or_2
ร่วมกันในแอตทริบิวต์ที่ต่างกันได้ ซึ่งต่างจาก selects.with_or
นี่เป็นข้อผิดพลาดของหลายเงื่อนไขในการจับคู่ ยกเว้นกรณีใดเงื่อนไขหนึ่งเป็น "ความเชี่ยวชาญพิเศษ" อื่นๆ ที่ไม่ชัดเจน หรือเงื่อนไขทั้งหมดแก้ไขเป็นค่าเดียวกัน ดูรายละเอียดได้ที่นี่
เชน AND
หากคุณต้องการสาขา select
เพื่อให้ตรงกับเงื่อนไขหลายรายการ ให้ใช้มาโคร Skylib config_setting_group:
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_and_2",
match_all = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_and_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
config_setting
ที่มีอยู่จะAND
ภายใน select
โดยตรงไม่ได้ ซึ่งต่างจากการเชื่อมโยง OR แบบเชน คุณต้องรวมรหัสเหล่านี้ไว้ใน config_setting_group
อย่างชัดเจน
ข้อความแสดงข้อผิดพลาดที่กำหนดเอง
โดยค่าเริ่มต้น เมื่อไม่มีเงื่อนไขที่ตรงกัน ระบบจะแนบเป้าหมาย select()
ไม่สำเร็จโดยมีข้อผิดพลาดดังนี้
ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//tools/cc_target_os:darwin
//tools/cc_target_os:android
ซึ่งปรับแต่งได้ด้วยแอตทริบิวต์ no_match_error
ดังนี้
cc_library(
name = "my_lib",
deps = select(
{
"//tools/cc_target_os:android": [":android_deps"],
"//tools/cc_target_os:windows": [":windows_deps"],
},
no_match_error = "Please build with an Android or Windows toolchain",
),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain
ความเข้ากันได้ของกฎ
การใช้งานกฎจะได้รับค่าที่ได้รับการแก้ไขของแอตทริบิวต์ที่กำหนดค่าได้ ตัวอย่างเช่น ให้ข้อมูลต่อไปนี้
# myapp/BUILD
some_rule(
name = "my_target",
some_attr = select({
":foo_mode": [":foo"],
":bar_mode": [":bar"],
}),
)
$ bazel build //myapp/my_target --define mode=foo
โค้ดการใช้งานกฎจะเห็น ctx.attr.some_attr
เป็น [":foo"]
มาโครจะยอมรับอนุประโยค select()
และส่งไปยังกฎแบบเนทีฟได้ แต่ควบคุมการกระทำเหล่านี้โดยตรงไม่ได้ เช่น มาโครจะไม่มีทางแปลง
select({"foo": "val"}, ...)
ถึง
select({"foo": "val_with_suffix"}, ...)
เนื่องจากเหตุผล 2 ประการ
ก่อนอื่น มาโครที่จำเป็นต้องทราบว่า select
จะเลือกเส้นทางใดใช้งานไม่ได้
เนื่องจากมาโครจะได้รับการประเมินในระยะการโหลดของ Bazel ซึ่งเกิดขึ้นก่อนที่จะรู้ค่าแฟล็ก
นี่คือข้อจำกัดหลักของการออกแบบของ Bazel ที่มีแนวโน้มว่าจะไม่เปลี่ยนแปลงในเร็วๆ นี้
อย่างที่ 2 มาโครที่ต้องทำซ้ำตามเส้นทางทั้งหมด select
แม้ในทางเทคนิคจะเป็นไปได้ แต่ขาด UI ที่สอดคล้องกัน การออกแบบเพิ่มเติมนั้นจำเป็น
ต่อการเปลี่ยนแปลงนี้
คำค้นหาและ cquery ของ Bazel
Bazel query
ทำงานในระยะการบรรทุกของ Bazel
ซึ่งหมายความว่าระบบไม่ทราบว่าจะใช้บรรทัดคำสั่งที่แฟล็กเป้าหมายใด เนื่องจากแฟล็กเหล่านั้นไม่ได้รับการประเมินจนกว่าจะถึงช่วงหลังของบิลด์ (ในระยะการวิเคราะห์)
จึงระบุไม่ได้ว่าเลือก select()
สาขาใด
Bazel cquery
ทำงานหลังช่วงการวิเคราะห์ของ Bazel จึงมีข้อมูลทั้งหมดนี้และแก้ไข select()
ได้อย่างแม่นยำ
โปรดพิจารณาสิ่งต่อไปนี้
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD
string_flag(
name = "dog_type",
build_setting_default = "cat"
)
cc_library(
name = "my_lib",
deps = select({
":long": [":foo_dep"],
":short": [":bar_dep"],
}),
)
config_setting(
name = "long",
flag_values = {":dog_type": "dachshund"},
)
config_setting(
name = "short",
flag_values = {":dog_type": "pug"},
)
query
มีค่าใกล้เคียงทรัพยากร Dependency ของ :my_lib
มากเกินไป:
$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep
ขณะที่ cquery
แสดงทรัพยากร Dependency ที่แน่นอน
$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep
คำถามที่พบบ่อย
เพราะเหตุใด Select() ในมาโครจึงใช้งานไม่ได้
Select() ทำงานได้ในกฎ ดูรายละเอียดได้ที่ความเข้ากันได้ของกฎ
ปัญหาสำคัญที่คำถามนี้มักจะหมายถึงว่า select() ใช้งานไม่ได้ในมาโคร ซึ่งแตกต่างจากกฎ ดูเอกสารเกี่ยวกับกฎและมาโครเพื่อทำความเข้าใจความแตกต่าง ต่อไปนี้เป็นตัวอย่างตั้งแต่ต้นจนจบ
กำหนดกฎและมาโคร:
# myapp/defs.bzl
# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
name = ctx.attr.name
allcaps = ctx.attr.my_config_string.upper() # This works fine on all values.
print("My name is " + name + " with custom message: " + allcaps)
# Rule declaration:
my_custom_bazel_rule = rule(
implementation = _impl,
attrs = {"my_config_string": attr.string()},
)
# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
allcaps = my_config_string.upper() # This line won't work with select(s).
print("My name is " + name + " with custom message: " + allcaps)
เริ่มต้นกฎและมาโครดังนี้
# myapp/BUILD
load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")
my_custom_bazel_rule(
name = "happy_rule",
my_config_string = select({
"//third_party/bazel_platforms/cpu:x86_32": "first string",
"//third_party/bazel_platforms/cpu:ppc": "second string",
}),
)
my_custom_bazel_macro(
name = "happy_macro",
my_config_string = "fixed string",
)
my_custom_bazel_macro(
name = "sad_macro",
my_config_string = select({
"//third_party/bazel_platforms/cpu:x86_32": "first string",
"//third_party/bazel_platforms/cpu:ppc": "other string",
}),
)
อาคารล้มเหลวเนื่องจาก sad_macro
ไม่สามารถประมวลผล select()
:
$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.
การสร้างจะประสบความสำเร็จเมื่อคุณแสดงความคิดเห็นเกี่ยวกับ sad_macro
:
# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.
สิ่งนี้เปลี่ยนแปลงไม่ได้เพราะมีการประเมินมาโครตามคำจำกัดความก่อนที่ Bazel จะอ่านแฟล็กบรรทัดคำสั่งของบิลด์ นั่นหมายความว่ามีข้อมูลไม่เพียงพอที่จะประเมิน Select()
แต่มาโครจะส่ง select()
เป็น BLOB ที่ไม่ชัดเจนไปยังกฎได้ดังนี้
# myapp/defs.bzl
def my_custom_bazel_macro(name, my_config_string):
print("Invoking macro " + name)
my_custom_bazel_rule(
name = name + "_as_target",
my_config_string = my_config_string,
)
$ bazel build //myapp:sad_macro_less_sad
DEBUG: /myworkspace/myapp/defs.bzl:23:3: Invoking macro sad_macro_less_sad.
DEBUG: /myworkspace/myapp/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING.
เหตุใด Select() จึงแสดงผลเป็น "จริง" เสมอ
เนื่องจากตามคำจำกัดความแล้วมาโคร (แต่ไม่ใช่กฎ) ไม่สามารถประเมิน select()
ได้ ความพยายามใดๆ ในการดำเนินการดังกล่าวจะก่อให้เกิดข้อผิดพลาด
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
บูลีนเป็นกรณีพิเศษที่ล้มเหลวโดยไม่มีการแจ้งเตือน ดังนั้นคุณจึงควรระมัดระวังในเรื่องดังกล่าวเป็นพิเศษ
$ cat myapp/defs.bzl
def my_boolean_macro(boolval):
print("TRUE" if boolval else "FALSE")
$ cat myapp/BUILD
load("//myapp:defs.bzl", "my_boolean_macro")
my_boolean_macro(
boolval = select({
"//third_party/bazel_platforms/cpu:x86_32": True,
"//third_party/bazel_platforms/cpu:ppc": False,
}),
)
$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
ปัญหานี้เกิดขึ้นเนื่องจากมาโครไม่เข้าใจเนื้อหาของ select()
ดังนั้น สิ่งที่นักการตลาดจะต้องประเมินก็คือออบเจ็กต์ select()
เอง ตามมาตรฐานการออกแบบของ Pythonic ออบเจ็กต์ทั้งหมดยกเว้นข้อยกเว้นจำนวนน้อยมากจะแสดงค่า "จริง" โดยอัตโนมัติ
ฉันอ่าน Select() เหมือนคำสั่งได้ไหม
มาโครประเมินรายการที่เลือกไม่ได้เนื่องจากมาโครจะประเมินก่อนที่ Bazel จะรู้ว่าพารามิเตอร์บรรทัดคำสั่งของบิลด์คืออะไร อย่างน้อยผู้อ่านจะอ่านพจนานุกรมของ select()
ได้ไหม เช่น เพิ่มคำต่อท้ายให้กับแต่ละค่า
ตามหลักแล้วก็สามารถทำได้ แต่ปัจจุบันยังไม่ได้เป็นฟีเจอร์ Bazel
สิ่งที่คุณทำได้ในปัจจุบันคือการเตรียมพจนานุกรมโดยตรง แล้วฟีดลงใน
select()
:
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
+ " > $@"
)
$ cat myapp/BUILD
selecty_genrule(
name = "selecty",
select_cmd = {
"//third_party/bazel_platforms/cpu:x86_32": "x86 mode",
},
)
$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX
หากคุณต้องการรองรับทั้งประเภท select()
และโฆษณาเนทีฟ ให้ดำเนินการดังนี้
$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
cmd_suffix = ""
if type(select_cmd) == "string":
cmd_suffix = select_cmd + " WITH SUFFIX"
elif type(select_cmd) == "dict":
for key in select_cmd.keys():
select_cmd[key] += " WITH SUFFIX"
cmd_suffix = select(select_cmd + {"//conditions:default": "default"})
native.genrule(
name = name,
outs = [name + ".out"],
srcs = [],
cmd = "echo " + cmd_suffix + "> $@",
)
เหตุใด select() จึงใช้งานไม่ได้กับ bind()
ก่อนอื่น อย่าใช้ bind()
เนื่องจากเลิกใช้งานเพื่อใช้ alias()
แทน
คำตอบทางเทคนิคคือ bind()
เป็นกฎที่เก็บ ไม่ใช่กฎการสร้าง
กฎที่เก็บไม่มีการกำหนดค่าที่เฉพาะเจาะจง และจะไม่มีการประเมินในลักษณะเดียวกันกับกฎการสร้าง ดังนั้น select()
ใน bind()
จึงประเมิน Branch ที่เฉพาะเจาะจงใดๆ ไม่ได้
คุณควรใช้ alias()
ที่มี select()
ในแอตทริบิวต์ actual
เพื่อระบุรันไทม์ประเภทนี้แทน ซึ่งทํางานได้อย่างถูกต้อง เนื่องจาก alias()
เป็นกฎการสร้าง และได้รับการประเมินด้วยการกําหนดค่าเฉพาะ
$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)
$ cat BUILD
config_setting(
name = "alt_ssl",
define_values = {
"ssl_library": "alternative",
},
)
alias(
name = "ssl",
actual = select({
"//:alt_ssl": "@alternative//:ssl",
"//conditions:default": "@boringssl//:ssl",
}),
)
การตั้งค่านี้ช่วยให้คุณส่ง --define ssl_library=alternative
ได้ และเป้าหมายใดๆ ที่ขึ้นอยู่กับ //:ssl
หรือ //external:ssl
จะเห็นทางเลือกนั้นอยู่ที่ @alternative//:ssl
แต่ควรหยุดใช้ bind()
ก่อน
เหตุใด Select() ของฉันจึงไม่เลือกสิ่งที่คาดหวัง
หาก //myapp:foo
มี select()
ที่ไม่ได้เลือกเงื่อนไขที่คาดไว้ ให้ใช้ cquery และ bazel config
เพื่อแก้ไขข้อบกพร่อง ดังนี้
หาก //myapp:foo
คือเป้าหมายระดับบนสุดที่คุณกำลังสร้าง ให้เรียกใช้
$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)
หากคุณกำลังสร้างเป้าหมาย //bar
อื่นที่อาศัย
//myapp:foo ในบางส่วนของกราฟย่อย ให้เรียกใช้คำสั่งต่อไปนี้
$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)
(12e23b9a2b534a)
ข้าง //myapp:foo
คือแฮชของการกำหนดค่าที่แปลค่า select()
ของ //myapp:foo
คุณตรวจสอบค่าได้โดยใช้ bazel config
ดังนี้
$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
cpu: darwin
compilation_mode: fastbuild
...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
linkopt: [-Dfoo=bar]
...
}
...
จากนั้นเปรียบเทียบผลลัพธ์นี้กับการตั้งค่าที่ config_setting
แต่ละรายการคาดไว้
//myapp:foo
อาจมีการกำหนดค่าต่างกันในเวอร์ชันเดียวกัน ดูเอกสารการค้นหาเพื่อดูคำแนะนำเกี่ยวกับการใช้ somepath
เพื่อเข้าถึงเอกสารที่ต้องการ
เหตุใด select()
จึงใช้ไม่ได้กับแพลตฟอร์ม
Bazel ไม่รองรับแอตทริบิวต์ที่กำหนดค่าได้ในการตรวจสอบว่าแพลตฟอร์มที่ระบุเป็นแพลตฟอร์มเป้าหมายหรือไม่ เนื่องจากอรรถศาสตร์ไม่ชัดเจน
เช่น
platform(
name = "x86_linux_platform",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
ในไฟล์ BUILD
นี้ คุณควรใช้ select()
ใดหากแพลตฟอร์มเป้าหมายมีทั้งข้อจำกัด @platforms//cpu:x86
และ @platforms//os:linux
แต่ไม่ได้กำหนด :x86_linux_platform
ไว้ที่นี่ ผู้เขียนไฟล์ BUILD
และผู้ใช้ที่กำหนดแพลตฟอร์มแยกต่างหากอาจมีแนวคิดที่แตกต่างกัน
จะให้ฉันทำยังไงแทนดี
แต่ให้กำหนด config_setting
ที่ตรงกับแพลตฟอร์มใดก็ได้ที่มีข้อจำกัดเหล่านี้แทน
config_setting(
name = "is_x86_linux",
constraint_values = [
"@platforms//cpu:x86",
"@platforms//os:linux",
],
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_x86_linux": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
กระบวนการนี้จะกำหนดความหมายที่เฉพาะเจาะจง ซึ่งทำให้ผู้ใช้เข้าใจได้อย่างชัดเจนว่าแพลตฟอร์มใดเป็นไปตามเงื่อนไขที่ต้องการ
ฉันต้องทำอย่างไรหากต้องการ select
บนแพลตฟอร์มจริงๆ
หากข้อกำหนดของบิลด์กำหนดให้ตรวจสอบแพลตฟอร์มโดยเฉพาะ คุณสามารถกลับค่าของแฟล็ก --platforms
ใน config_setting
ได้ดังนี้
config_setting(
name = "is_specific_x86_linux_platform",
values = {
"platforms": ["//package:x86_linux_platform"],
},
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
ทีม Bazel ไม่แนะนำให้ทำเช่นนี้ แต่เป็นการจำกัดบิลด์ของคุณมากเกินไป และทำให้ผู้ใช้สับสนเมื่อเงื่อนไขที่คาดไว้ไม่ตรงกัน