หน้านี้ครอบคลุมถึงประโยชน์และการใช้งานพื้นฐานของการกำหนดค่า Starlark ซึ่งเป็น API ของ Bazel สำหรับการปรับแต่งวิธีสร้างโปรเจ็กต์ โดยจะอธิบายวิธีกำหนดการตั้งค่าบิลด์และแสดงตัวอย่าง
ซึ่งจะช่วยให้คุณทำสิ่งต่อไปนี้ได้
- กำหนดแฟล็กที่กำหนดเองสำหรับโปรเจ็กต์ ซึ่งไม่จำเป็นต้องใช้
--define - เขียน
ทรานซิชันเพื่อกำหนดค่าการขึ้นต่อกันใน
การกำหนดค่าที่แตกต่างจากองค์ประกอบระดับบน
(เช่น
--compilation_mode=optหรือ--cpu=arm) - กำหนดค่าเริ่มต้นที่ดีขึ้นในกฎ (เช่น สร้าง
//my:android_appด้วย SDK ที่ระบุโดยอัตโนมัติ)
และอื่นๆ อีกมากมาย ทั้งหมดนี้ทำได้จากไฟล์ .bzl (ไม่จำเป็นต้องใช้ Bazel เวอร์ชันที่เผยแพร่) ดูตัวอย่างได้ที่ที่เก็บ
bazelbuild/examples สำหรับ
ตัวอย่าง
การตั้งค่าบิลด์ที่ผู้ใช้กำหนด
การตั้งค่าบิลด์คือข้อมูลการกำหนดค่ารายการเดียว
การกำหนดค่า
ให้คิดว่าการกำหนดค่าเป็นการแมปคีย์/ค่า การตั้งค่า --cpu=ppc
และ --copt="-DFoo" จะสร้างการกำหนดค่าที่มีลักษณะเป็น
{cpu: ppc, copt: "-DFoo"} โดยแต่ละรายการเป็นการตั้งค่าบิลด์
แฟล็กแบบเดิม เช่น cpu และ copt เป็นการตั้งค่าดั้งเดิม ซึ่งมีการกำหนดคีย์และตั้งค่าภายในโค้ด Java ของ Bazel ดั้งเดิม
ผู้ใช้ Bazel จะอ่านและเขียนแฟล็กเหล่านี้ได้ผ่านบรรทัดคำสั่งและ API อื่นๆ ที่ดูแลรักษาแบบดั้งเดิมเท่านั้น การเปลี่ยนแฟล็กดั้งเดิมและ API ที่แสดงแฟล็กเหล่านั้นต้องใช้ Bazel เวอร์ชันที่เผยแพร่ การตั้งค่าบิลด์ที่ผู้ใช้กำหนดจะกำหนดไว้ในไฟล์ .bzl (จึงไม่จำเป็นต้องใช้ Bazel เวอร์ชันที่เผยแพร่เพื่อลงทะเบียนการเปลี่ยนแปลง) นอกจากนี้ ยังตั้งค่าผ่านบรรทัดคำสั่งได้ด้วย
(หากกำหนดให้เป็น flags โปรดดูรายละเอียดเพิ่มเติมด้านล่าง) แต่ก็ตั้งค่าผ่านทรานซิชันที่ผู้ใช้กำหนดได้เช่นกัน
การกำหนดการตั้งค่าบิลด์
พารามิเตอร์ build_setting ของ rule()
การตั้งค่าบิลด์เป็นกฎเหมือนกับกฎอื่นๆ และแยกความแตกต่างโดยใช้
แอตทริบิวต์ build_setting
ของฟังก์ชันrule() Starlark
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
แอตทริบิวต์ build_setting รับฟังก์ชันที่กำหนดประเภทของการตั้งค่าบิลด์ ประเภทจะจำกัดอยู่เพียงชุดประเภท Starlark พื้นฐาน เช่น bool และ string ดูรายละเอียดได้ในเอกสารประกอบของโมดูล config
การพิมพ์ที่ซับซ้อนมากขึ้นสามารถทำได้ในฟังก์ชันการใช้งานของกฎ ดูรายละเอียดเพิ่มเติมด้านล่าง
ฟังก์ชันของโมดูล config รับพารามิเตอร์บูลีนที่ไม่บังคับ flag
ซึ่งตั้งค่าเป็น "เท็จ" โดยค่าเริ่มต้น หากตั้งค่า flag เป็น "จริง" ผู้ใช้จะตั้งค่าบิลด์ได้ในบรรทัดคำสั่ง
รวมถึงผู้เขียนกฎจะตั้งค่าภายในผ่านค่าเริ่มต้นและ ทรานซิชัน
ผู้ใช้ไม่ควรตั้งค่าบางอย่างได้ ตัวอย่างเช่น หากคุณในฐานะผู้เขียนกฎมีโหมดแก้ไขข้อบกพร่องที่ต้องการเปิดใช้ภายในกฎการทดสอบ คุณไม่ต้องการให้ผู้ใช้เปิดใช้ฟีเจอร์นั้นภายในกฎอื่นๆ ที่ไม่ใช่กฎการทดสอบโดยไม่เลือกปฏิบัติ
การใช้ ctx.build_setting_value
กฎการตั้งค่าบิลด์มี ฟังก์ชันการใช้งาน เหมือนกับกฎอื่นๆ
คุณเข้าถึงค่าประเภท Starlark พื้นฐานของการตั้งค่าบิลด์ได้ผ่านเมธอด ctx.build_setting_value เมธอดนี้ใช้ได้กับ
ctx ออบเจ็กต์ของกฎการตั้งค่าบิลด์เท่านั้น เมธอดการใช้งานเหล่านี้สามารถส่งต่อค่าการตั้งค่าบิลด์ได้โดยตรง หรือดำเนินการเพิ่มเติมกับค่าดังกล่าว เช่น การตรวจสอบประเภทหรือการสร้างโครงสร้างที่ซับซ้อนมากขึ้น วิธีใช้งานการตั้งค่าบิลด์ประเภท enum
# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])
temperatures = ["HOT", "LUKEWARM", "ICED"]
def _impl(ctx):
raw_temperature = ctx.build_setting_value
if raw_temperature not in temperatures:
fail(str(ctx.label) + " build setting allowed to take values {"
+ ", ".join(temperatures) + "} but was set to unallowed value "
+ raw_temperature)
return TemperatureProvider(type = raw_temperature)
temperature = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
การกำหนดแฟล็กสตริงแบบหลายค่า
การตั้งค่าสตริงมีพารามิเตอร์ allow_multiple เพิ่มเติม ซึ่งอนุญาตให้ตั้งค่าแฟล็กหลายครั้งในบรรทัดคำสั่งหรือใน bazelrc ค่าเริ่มต้นยังคงตั้งค่าด้วยแอตทริบิวต์ประเภทสตริง
# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
name = "roasts",
build_setting_default = "medium"
)
ระบบจะถือว่าการตั้งค่าแต่ละรายการของแฟล็กเป็นค่าเดียว
$ bazel build //my/target --//example:roasts=blonde \
--//example:roasts=medium,dark
ระบบจะแยกวิเคราะห์ข้อมูลข้างต้นเป็น {"//example:roasts": ["blonde", "medium,dark"]} และ
ctx.build_setting_value จะแสดงผลรายการ ["blonde", "medium,dark"]
การสร้างอินสแตนซ์การตั้งค่าบิลด์
กฎที่กำหนดด้วยพารามิเตอร์ build_setting มีแอตทริบิวต์ที่บังคับโดยนัย
build_setting_default แอตทริบิวต์นี้มีประเภทเดียวกับที่ประกาศโดยพารามิเตอร์ build_setting
# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])
def _impl(ctx):
return FlavorProvider(type = ctx.build_setting_value)
flavor = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
การตั้งค่าที่กำหนดไว้ล่วงหน้า
ไลบรารี Skylib มีชุดการตั้งค่าที่กำหนดไว้ล่วงหน้าซึ่งคุณสร้างอินสแตนซ์ได้โดยไม่ต้อง เขียน Starlark ที่กำหนดเอง
ตัวอย่างเช่น หากต้องการกำหนดการตั้งค่าที่ยอมรับค่าสตริงที่จำกัด
# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "myflag",
values = ["a", "b", "c"],
build_setting_default = "a",
)
ดูรายการทั้งหมดได้ที่ กฎการตั้งค่าบิลด์ทั่วไป
การใช้การตั้งค่าบิลด์
การขึ้นต่อกันกับการตั้งค่าบิลด์
หากเป้าหมายต้องการอ่านข้อมูลการกำหนดค่า เป้าหมายจะขึ้นต่อกันกับการตั้งค่าบิลด์ได้โดยตรงผ่านการขึ้นต่อกันของทรัพยากร Dependency ของแอตทริบิวต์ปกติ
# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
...
drink_rule = rule(
implementation = _rule_impl,
attrs = {
"flavor": attr.label()
}
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
drink_rule(
name = "my_drink",
flavor = ":favorite_flavor",
)
ภาษาต่างๆ อาจต้องการสร้างชุดการตั้งค่าบิลด์มาตรฐานที่กฎทั้งหมดสำหรับภาษานั้นขึ้นต่อกัน แม้ว่าแนวคิดดั้งเดิมของ fragments จะไม่มีอยู่อีกต่อไปในฐานะออบเจ็กต์ที่ฮาร์ดโค้ดไว้ในโลกของการกำหนดค่า Starlark แต่วิธีหนึ่งในการแปลแนวคิดนี้คือการใช้ชุดแอตทริบิวต์โดยนัยทั่วไป เช่น
# kotlin/rules.bzl
_KOTLIN_CONFIG = {
"_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
"_mode": attr.label(default = "//kotlin/config:mode-flag"),
...
}
...
kotlin_library = rule(
implementation = _rule_impl,
attrs = dicts.add({
"library-attr": attr.string()
}, _KOTLIN_CONFIG)
)
kotlin_binary = rule(
implementation = _binary_impl,
attrs = dicts.add({
"binary-attr": attr.label()
}, _KOTLIN_CONFIG)
การใช้การตั้งค่าบิลด์ในบรรทัดคำสั่ง
คุณใช้บรรทัดคำสั่งเพื่อตั้งค่าบิลด์
ที่ทำเครื่องหมายเป็นแฟล็กได้ ซึ่งคล้ายกับแฟล็กดั้งเดิมส่วนใหญ่ ชื่อของการตั้งค่าบิลด์คือเส้นทางเป้าหมายแบบเต็มโดยใช้ไวยากรณ์ name=value
$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed
ระบบรองรับไวยากรณ์บูลีนพิเศษ
$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag
การใช้นามแฝงของการตั้งค่าบิลด์
คุณสามารถตั้งนามแฝงสำหรับเส้นทางเป้าหมายของการตั้งค่าบิลด์เพื่อให้ง่ายต่อการอ่านในบรรทัดคำสั่ง นามแฝงทำงานคล้ายกับแฟล็กดั้งเดิมและใช้ไวยากรณ์ตัวเลือกขีดกลาง 2 ขีดด้วย
ตั้งอีเมลแทนโดยเพิ่ม --flag_alias=ALIAS_NAME=TARGET_PATH ลงใน .bazelrc ตัวอย่างเช่น หากต้องการตั้งอีเมลแทนเป็น coffee
# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp
แนวทางปฏิบัติแนะนำ: การตั้งนามแฝงหลายครั้งจะทำให้รายการล่าสุดมีความสำคัญสูงสุด ใช้ชื่อนามแฝงที่ไม่ซ้ำกันเพื่อหลีกเลี่ยงผลการแยกวิเคราะห์ที่ไม่ต้องการ
หากต้องการใช้นามแฝง ให้พิมพ์นามแฝงแทนเส้นทางเป้าหมายของการตั้งค่าบิลด์
ตัวอย่างการตั้งค่า coffee ใน .bazelrc ของผู้ใช้
$ bazel build //my/target --coffee=ICED
แทนที่จะเป็น
$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED
แนวทางปฏิบัติแนะนำ: แม้ว่าจะตั้งนามแฝงในบรรทัดคำสั่งได้ แต่การเก็บนามแฝงไว้ใน .bazelrc จะช่วยลดความยุ่งเหยิงในบรรทัดคำสั่ง
การตั้งค่าบิลด์ประเภทป้ายกำกับ
การตั้งค่าประเภทป้ายกำกับไม่สามารถกำหนดได้โดยใช้พารามิเตอร์กฎ build_setting ซึ่งต่างจากการตั้งค่าบิลด์อื่นๆ แต่ Bazel มีกฎในตัว 2 ข้อ ได้แก่ label_flag และ label_setting กฎเหล่านี้จะส่งต่อผู้ให้บริการของเป้าหมายจริงที่ตั้งค่าบิลด์ ทรานซิชันสามารถอ่าน/เขียน label_flag และ label_setting ได้ และผู้ใช้สามารถตั้งค่า label_flag ได้เหมือนกับกฎ build_setting อื่นๆ ความแตกต่างเพียงอย่างเดียวคือไม่สามารถกำหนดเองได้
การตั้งค่าประเภทป้ายกำกับจะแทนที่ฟังก์ชันการทำงานของค่าเริ่มต้นที่ผูกไว้ในภายหลังในที่สุด แอตทริบิวต์ค่าเริ่มต้นที่ผูกไว้ในภายหลังคือแอตทริบิวต์ประเภทป้ายกำกับที่การกำหนดค่าอาจส่งผลต่อค่าสุดท้าย ใน Starlark การตั้งค่านี้จะแทนที่
configuration_field
API
# example/rules.bzl
MyProvider = provider(fields = ["my_field"])
def _dep_impl(ctx):
return MyProvider(my_field = "yeehaw")
dep_rule = rule(
implementation = _dep_impl
)
def _parent_impl(ctx):
if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
...
parent_rule = rule(
implementation = _parent_impl,
attrs = { "my_field_provider": attr.label() }
)
# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")
dep_rule(name = "dep")
parent_rule(name = "parent", my_field_provider = ":my_field_provider")
label_flag(
name = "my_field_provider",
build_setting_default = ":dep"
)
การตั้งค่าบิลด์และ select()
ผู้ใช้สามารถกำหนดค่าแอตทริบิวต์ในการตั้งค่าบิลด์ได้โดยใช้
select() เป้าหมายการตั้งค่าบิลด์สามารถส่งผ่านไปยังแอตทริบิวต์ flag_values ของ config_setting ค่าที่จะจับคู่กับการกำหนดค่าจะส่งผ่านเป็น String จากนั้นแยกวิเคราะห์เป็นประเภทของการตั้งค่าบิลด์เพื่อการจับคู่
config_setting(
name = "my_config",
flag_values = {
"//example:favorite_flavor": "MANGO"
}
)
ทรานซิชันที่ผู้ใช้กำหนด
ทรานซิชันการกำหนดค่า จะแมปการแปลงจากเป้าหมายที่กำหนดค่าหนึ่งไปยังอีกเป้าหมายหนึ่งภายใน กราฟบิลด์
การกำหนด
ทรานซิชันกำหนดการเปลี่ยนแปลงการกำหนดค่าระหว่างกฎ ตัวอย่างเช่น ทรานซิชันจะจัดการคำขอ เช่น "คอมไพล์ทรัพยากร Dependency ของฉันสำหรับ CPU อื่นที่ไม่ใช่ CPU ขององค์ประกอบระดับบน"
อย่างเป็นทางการ ทรานซิชันคือฟังก์ชันจากการกำหนดค่าอินพุตเป็นการกำหนดค่าเอาต์พุตอย่างน้อย 1 รายการ ทรานซิชันส่วนใหญ่เป็นแบบ 1:1 เช่น "ลบล้างการกำหนดค่าอินพุตด้วย --cpu=ppc" นอกจากนี้ ยังมีทรานซิชันแบบ 1:2 ขึ้นไป แต่มีข้อจำกัดพิเศษ
ใน Starlark ทรานซิชันจะกำหนดไว้คล้ายกับกฎ โดยมีฟังก์ชัน
transition()
ที่กำหนด
และฟังก์ชันการใช้งาน
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//example:favorite_flavor" : "MINT"}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
ฟังก์ชัน transition() รับฟังก์ชันการใช้งาน ชุดการตั้งค่าบิลด์ที่จะอ่าน(inputs) และชุดการตั้งค่าบิลด์ที่จะเขียน (outputs) ฟังก์ชันการใช้งานมีพารามิเตอร์ 2 รายการ ได้แก่ settings และ attr settings คือพจนานุกรม {String:Object} ของการตั้งค่าทั้งหมดที่ประกาศในพารามิเตอร์ inputs เป็น transition()
attr คือพจนานุกรมของแอตทริบิวต์และค่าของกฎที่แนบทรานซิชัน เมื่อแนบเป็นทรานซิชันขอบขาออก ค่าของแอตทริบิวต์เหล่านี้จะได้รับการกำหนดค่าทั้งหมดหลังจากการแก้ปัญหา select() เมื่อแนบเป็น
an ทรานซิชันขอบขาเข้า, attr does not
รวมแอตทริบิวต์ใดๆ ที่ใช้ตัวเลือกเพื่อแก้ปัญหาค่า หากทรานซิชันขอบขาเข้าใน --foo อ่านแอตทริบิวต์ bar แล้วเลือก --foo เพื่อตั้งค่าแอตทริบิวต์ bar ก็อาจเป็นไปได้ที่ทรานซิชันขอบขาเข้าจะอ่านค่า bar ที่ไม่ถูกต้องในทรานซิชัน
ฟังก์ชันการใช้งานต้องแสดงผลพจนานุกรม (หรือรายการพจนานุกรม ในกรณีของทรานซิชันที่มีการกำหนดค่าเอาต์พุตหลายรายการ) ของค่าการตั้งค่าบิลด์ใหม่ที่จะนำไปใช้ ชุดคีย์ของพจนานุกรมที่แสดงผลต้องมีชุดการตั้งค่าบิลด์ที่ส่งผ่านไปยังพารามิเตอร์ outputs ของฟังก์ชันทรานซิชันอย่างแน่นอน ข้อกำหนดนี้เป็นจริงแม้ว่าการตั้งค่าบิลด์จะไม่ได้เปลี่ยนแปลงไปตลอดทรานซิชันก็ตาม ค่าเดิมจะต้องส่งผ่านอย่างชัดเจนในพจนานุกรมที่แสดงผล
การกำหนดทรานซิชันแบบ 1:2 ขึ้นไป
ทรานซิชันขอบขาออก สามารถแมปการกำหนดค่าอินพุตเดียวกับการกำหนดค่าเอาต์พุตอย่างน้อย 2 รายการ การดำเนินการนี้มีประโยชน์สำหรับการกำหนดกฎที่รวมโค้ดหลายสถาปัตยกรรม
ทรานซิชันแบบ 1:2 ขึ้นไปจะกำหนดโดยการแสดงผลรายการพจนานุกรมในฟังก์ชันการใช้งานทรานซิชัน
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return [
{"//example:favorite_flavor" : "LATTE"},
{"//example:favorite_flavor" : "MOCHA"},
]
coffee_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
นอกจากนี้ ยังตั้งค่าคีย์ที่กำหนดเองซึ่งฟังก์ชันการใช้งานกฎสามารถใช้เพื่ออ่านทรัพยากร Dependency แต่ละรายการได้ด้วย
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
การแนบทรานซิชัน
คุณแนบทรานซิชันได้ 2 ที่ ได้แก่ ขอบขาเข้าและขอบขาออก ซึ่งหมายความว่ากฎสามารถเปลี่ยนการกำหนดค่าของตัวเอง (การเปลี่ยนผ่านขอบขาเข้า) และเปลี่ยนการกำหนดค่าของทรัพยากร Dependency (การเปลี่ยนผ่านขอบขาออก)
หมายเหตุ: ปัจจุบันยังไม่มีวิธีแนบทรานซิชัน Starlark กับกฎดั้งเดิม หากต้องการทำเช่นนี้ โปรดติดต่อ bazel-discuss@googlegroups.com เพื่อขอความช่วยเหลือในการหาวิธีแก้ปัญหา
ทรานซิชันขอบขาเข้า
ทรานซิชันขอบขาเข้าจะเปิดใช้งานโดยการแนบออบเจ็กต์ transition
(สร้างโดย transition()) กับ rule()'s cfg พารามิเตอร์:
# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
implementation = _impl,
cfg = hot_chocolate_transition,
...
ทรานซิชันขอบขาเข้าต้องเป็นทรานซิชันแบบ 1:1
ทรานซิชันขอบขาออก
ทรานซิชันขอบขาออกจะเปิดใช้งานโดยการแนบออบเจ็กต์ transition
(สร้างโดย transition()) กับพารามิเตอร์ cfg ของแอตทริบิวต์
# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
implementation = _impl,
attrs = { "dep": attr.label(cfg = coffee_transition)}
...
ทรานซิชันขอบขาออกอาจเป็นแบบ 1:1 หรือ 1:2 ขึ้นไป
ดูวิธีอ่านคีย์เหล่านี้ได้ที่การเข้าถึงแอตทริบิวต์ด้วยทรานซิชัน
ทรานซิชันในตัวเลือกดั้งเดิม
ทรานซิชัน Starlark ยังประกาศการอ่านและการเขียนในตัวเลือกการกำหนดค่าบิลด์ดั้งเดิมได้ด้วยผ่านคำนำหน้าพิเศษสำหรับชื่อตัวเลือก
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//command_line_option:cpu": "k8"}
cpu_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
ตัวเลือกดั้งเดิมที่ไม่รองรับ
Bazel ไม่รองรับการเปลี่ยน --define ด้วย
"//command_line_option:define" แต่ให้ใช้การตั้งค่าบิลด์ที่กำหนดเองแทน
โดยทั่วไป เราไม่แนะนำให้ใช้ --define ใหม่ แต่ให้ใช้การตั้งค่าบิลด์แทน
Bazel ไม่รองรับการเปลี่ยน --config เนื่องจาก --config เป็นแฟล็ก "การขยาย" ที่ขยายเป็นแฟล็กอื่นๆ
ที่สำคัญคือ --config อาจมีแฟล็กที่ไม่ส่งผลต่อการกำหนดค่าบิลด์
เช่น
--spawn_strategy
Bazel ได้รับการออกแบบมาให้ไม่สามารถผูกแฟล็กดังกล่าวกับเป้าหมายแต่ละรายการได้ ซึ่งหมายความว่าไม่มีวิธีที่สอดคล้องกันในการนำแฟล็กเหล่านี้ไปใช้ในทรานซิชัน
วิธีแก้ปัญหาคือคุณสามารถแสดงรายการแฟล็กที่ เป็น ส่วนหนึ่งของการกำหนดค่าในทรานซิชันได้อย่างชัดเจน ซึ่งต้องดูแลรักษาการขยายของ --config ใน 2 ที่ ซึ่งเป็นข้อบกพร่องที่ทราบกันดีใน UI
ทรานซิชันในการตั้งค่าบิลด์ที่อนุญาตหลายรายการ
เมื่อตั้งค่าบิลด์ที่ อนุญาตหลายค่า คุณต้องตั้งค่าของ ค่าด้วยรายการ
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
# Using a value of just "dark" here will throw an error
return {"//example:roasts" : ["dark"]},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:roasts"]
)
ทรานซิชันแบบไม่มีการดำเนินการ
หากทรานซิชันแสดงผล {}, [] หรือ None แสดงว่าเป็นการย่อเพื่อคงการตั้งค่าทั้งหมดไว้ที่ค่าเดิม ซึ่งอาจสะดวกกว่าการตั้งค่าเอาต์พุตแต่ละรายการเป็นค่าเดิมอย่างชัดเจน
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (attr)
if settings["//example:already_chosen"] is True:
return {}
return {
"//example:favorite_flavor": "dark chocolate",
"//example:include_marshmallows": "yes",
"//example:desired_temperature": "38C",
}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = ["//example:already_chosen"],
outputs = [
"//example:favorite_flavor",
"//example:include_marshmallows",
"//example:desired_temperature",
]
)
การเข้าถึงแอตทริบิวต์ด้วยทรานซิชัน
เมื่อ แนบทรานซิชันกับขอบขาออก
(ไม่ว่าทรานซิชันจะเป็นแบบ 1:1 หรือ 1:2 ขึ้นไป) ctx.attr จะถูกบังคับให้เป็นรายการ
หากยังไม่ได้เป็น ลำดับขององค์ประกอบในรายการนี้ไม่ได้ระบุไว้
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
return {"//example:favorite_flavor" : "LATTE"},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
def _rule_impl(ctx):
# Note: List access even though "dep" is not declared as list
transitioned_dep = ctx.attr.dep[0]
# Note: Access doesn't change, other_deps was already a list
for other_dep in ctx.attr.other_deps:
# ...
coffee_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = coffee_transition)
"other_deps": attr.label_list(cfg = coffee_transition)
})
หากทรานซิชันเป็นแบบ 1:2+ และตั้งค่าคีย์ที่กำหนดเอง คุณจะใช้ ctx.split_attr เพื่ออ่านการขึ้นต่อกันแต่ละรายการสำหรับแต่ละคีย์ได้
# example/transitions/rules.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
def _rule_impl(ctx):
apple_dep = ctx.split_attr.dep["Apple deps"]
linux_dep = ctx.split_attr.dep["Linux deps"]
# ctx.attr has a list of all deps for all keys. Order is not guaranteed.
all_deps = ctx.attr.dep
multi_arch_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = multi_arch_transition)
})
ดูตัวอย่างฉบับเต็ม ได้ที่นี่
การผสานรวมกับแพลตฟอร์มและชุดเครื่องมือ
แฟล็กดั้งเดิมจำนวนมากในปัจจุบัน เช่น --cpu และ --crosstool_top เกี่ยวข้องกับการแก้ปัญหา Toolchain ในอนาคต ทรานซิชันที่ชัดเจนในแฟล็กประเภทนี้อาจถูกแทนที่ด้วยการเปลี่ยนแพลตฟอร์มเป้าหมาย
สิ่งที่ควรพิจารณาเกี่ยวกับหน่วยความจำและประสิทธิภาพ
การเพิ่มทรานซิชันและดังนั้นการกำหนดค่าใหม่ลงในบิลด์มีค่าใช้จ่าย ได้แก่ กราฟบิลด์ที่ใหญ่ขึ้น กราฟบิลด์ที่เข้าใจยากขึ้น และบิลด์ที่ช้าลง คุณควรพิจารณาค่าใช้จ่ายเหล่านี้เมื่อพิจารณาใช้ทรานซิชันในกฎบิลด์ ด้านล่างนี้เป็นตัวอย่างวิธีที่ทรานซิชันอาจทำให้กราฟบิลด์เติบโตแบบทวีคูณ
บิลด์ที่มีลักษณะการทำงานไม่ดี: กรณีศึกษา

รูปที่ 1 กราฟความสามารถในการปรับขนาดที่แสดงเป้าหมายระดับบนสุดและการขึ้นต่อกัน
กราฟนี้แสดงเป้าหมายระดับบนสุด //pkg:app ซึ่งขึ้นต่อกันกับเป้าหมาย 2 รายการ ได้แก่ //pkg:1_0 และ //pkg:1_1 เป้าหมายทั้ง 2 รายการนี้ขึ้นต่อกันกับเป้าหมาย 2 รายการ ได้แก่ //pkg:2_0 และ //pkg:2_1 เป้าหมายทั้ง 2 รายการนี้ขึ้นต่อกันกับเป้าหมาย 2 รายการ ได้แก่ //pkg:3_0 และ //pkg:3_1
และดำเนินการต่อไปจนถึง //pkg:n_0 และ //pkg:n_1 ซึ่งทั้ง 2 รายการขึ้นต่อกันกับเป้าหมายเดียว ได้แก่ //pkg:dep
การสร้าง //pkg:app ต้องใช้ \(2n+2\) เป้าหมายต่อไปนี้
//pkg:app//pkg:dep//pkg:i_0และ//pkg:i_1สำหรับ \(i\) in \([1..n]\)
ลองนึกภาพว่าคุณ ใช้ แฟล็ก
--//foo:owner=<STRING> และ //pkg:i_b ใช้
depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"
กล่าวอีกนัยหนึ่งคือ //pkg:i_b จะต่อท้าย b กับค่าเดิมของ --owner สำหรับการขึ้นต่อกันทั้งหมด
ซึ่งจะสร้างเป้าหมายที่กำหนดค่าต่อไปนี้:
//pkg:app //foo:owner=""
//pkg:1_0 //foo:owner=""
//pkg:1_1 //foo:owner=""
//pkg:2_0 (via //pkg:1_0) //foo:owner="0"
//pkg:2_0 (via //pkg:1_1) //foo:owner="1"
//pkg:2_1 (via //pkg:1_0) //foo:owner="0"
//pkg:2_1 (via //pkg:1_1) //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0) //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1) //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0) //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1) //foo:owner="11"
...
//pkg:dep สร้าง \(2^n\) เป้าหมายที่กำหนดค่า: config.owner=
"\(b_0b_1...b_n\)" สำหรับin ทั้งหมด \(b_i\) \(\{0,1\}\).
ซึ่งทำให้กราฟบิลด์ใหญ่กว่ากราฟเป้าหมายแบบทวีคูณ พร้อมผลกระทบต่อหน่วยความจำและประสิทธิภาพที่สอดคล้องกัน
TODO: เพิ่มกลยุทธ์สำหรับการวัดและการลดผลกระทบของปัญหาเหล่านี้
อ่านเพิ่มเติม
ดูรายละเอียดเพิ่มเติมเกี่ยวกับการแก้ไขการกำหนดค่าบิลด์ได้ที่
- การกำหนดค่าบิลด์ Starlark
- ชุดตัวอย่างแบบครบวงจร set