การกำหนดค่า

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4

หน้านี้กล่าวถึงประโยชน์และการใช้งานพื้นฐานของการกำหนดค่า Starlark ซึ่งเป็น API ของ Bazel สำหรับการปรับแต่งวิธีสร้างโปรเจ็กต์ ซึ่งรวมถึงวิธีกำหนดการตั้งค่าการสร้างและแสดงตัวอย่าง

ซึ่งช่วยให้คุณทำสิ่งต่อไปนี้ได้

  • กำหนด Flag ที่กำหนดเองสำหรับโปรเจ็กต์ของคุณโดยไม่ทำให้ต้องมี --define
  • เขียนทรานซิชันเพื่อกำหนดค่า deps ในการกําหนดค่าที่แตกต่างจากของพาเรนต์ (เช่น --compilation_mode=opt หรือ --cpu=arm)
  • สร้างค่าเริ่มต้นที่ดีขึ้นให้เป็นกฎ (เช่น สร้าง //my:android_app ด้วย SDK ที่ระบุโดยอัตโนมัติ)

และอีกมากมาย ทั้งหมดมาจากไฟล์ .bzl (ไม่ต้องมีรุ่น Bazel) ดูตัวอย่างใน bazelbuild/examples

การตั้งค่าบิลด์ที่ผู้ใช้กำหนด

การตั้งค่าบิลด์คือข้อมูลการกําหนดค่าเพียงรายการเดียว ให้คิดว่าการกําหนดค่าคือแมปคีย์/ค่า การตั้งค่า --cpu=ppc และ --copt="-DFoo" จะสร้างการกำหนดค่าที่มีลักษณะดังนี้ {cpu: ppc, copt: "-DFoo"} โดยแต่ละรายการคือการตั้งค่าบิลด์

Flag แบบดั้งเดิม เช่น cpu และ copt คือการตั้งค่าแบบเนทีฟ ซึ่งมีการกําหนดคีย์และตั้งค่าภายในโค้ด Java ของ Bazel เอง ผู้ใช้ Bazel จะอ่านและเขียนได้ผ่านบรรทัดคำสั่งและ API อื่นๆ ที่ดูแลรักษาแบบเนทีฟเท่านั้น การเปลี่ยน Flag เนทีฟและ API ที่เปิดเผย Flag ต้องใช้รุ่น Bazel การตั้งค่าบิลด์ที่ผู้ใช้กำหนดจะกำหนดไว้ในไฟล์ .bzl (จึงไม่จำเป็นต้องมีรุ่น Bazel เพื่อบันทึกการเปลี่ยนแปลง) นอกจากนี้ยังตั้งค่าผ่านบรรทัดคำสั่งได้ด้วย (หากกำหนดเป็น flags โปรดดูข้อมูลเพิ่มเติมด้านล่าง) แต่ก็ตั้งค่าผ่านการเปลี่ยนที่ผู้ใช้กำหนดได้ด้วย

การกำหนดการตั้งค่าบิลด์

ตัวอย่างแบบครบวงจร

พารามิเตอร์ build_setting rule()

การตั้งค่าบิลด์เป็นกฎเช่นเดียวกับกฎอื่นๆ และแยกความแตกต่างโดยใช้แอตทริบิวต์ของrule()ฟังก์ชัน Starlarkbuild_setting

# 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)
)

การกำหนด Flag สตริงที่มีหลายชุด

การตั้งค่าสตริงมีพารามิเตอร์ allow_multiple เพิ่มเติมซึ่งอนุญาตให้ตั้งค่า Flag ได้หลายครั้งในบรรทัดคำสั่งหรือใน bazelrcs ค่าเริ่มต้นของแอตทริบิวต์เหล่านี้จะยังคงกำหนดด้วยแอตทริบิวต์ประเภทสตริง

# 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"
)

ระบบจะถือว่าการตั้งค่าแต่ละรายการของ Flag เป็นค่าเดี่ยว ดังนี้

$ 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",
)

โปรดดูรายการที่สมบูรณ์ที่หัวข้อกฎการตั้งค่าการสร้างทั่วไป

การใช้การตั้งค่าบิลด์

ขึ้นอยู่กับการตั้งค่าบิลด์

หากเป้าหมายต้องการอ่านข้อมูลการกําหนดค่า เป้าหมายจะขึ้นอยู่กับการตั้งค่าบิลด์โดยตรงผ่านข้อกําหนดของแอตทริบิวต์ปกติ

# 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",
)

ภาษาอาจต้องการสร้างชุดการตั้งค่าเวอร์ชัน Canonical ซึ่งกฎทั้งหมดสำหรับภาษานั้นขึ้นอยู่กับ แม้ว่าแนวคิดดั้งเดิมของ 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)

ใช้การตั้งค่าบิลด์ในบรรทัดคำสั่ง

คุณสามารถใช้บรรทัดคำสั่งเพื่อตั้งค่าการสร้างที่มีการทำเครื่องหมายว่าเป็น Flag ได้ เช่นเดียวกับ Flag เนทีฟส่วนใหญ่ ชื่อของการตั้งค่าบิลด์คือเส้นทางเป้าหมายแบบเต็มโดยใช้ไวยากรณ์ 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

การใช้ชื่อแทนการตั้งค่าบิลด์

คุณสามารถสร้างชื่อแทนสำหรับเส้นทางเป้าหมายในการตั้งค่าบิลด์เพื่อให้อ่านบรรทัดคำสั่งได้ง่ายขึ้น ตัวแฝงทํางานคล้ายกับ 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 การดำเนินการนี้จะแทนที่ API ของ configuration_field

# 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"
    }
)

การเปลี่ยนที่ผู้ใช้กำหนด

การเปลี่ยนการกำหนดค่าจะแมปการเปลี่ยนรูปแบบจากเป้าหมายที่กำหนดค่าแล้วเป้าหมายอื่นภายในกราฟบิลด์

กฎที่กำหนดค่าดังกล่าวต้องมีแอตทริบิวต์พิเศษ ดังนี้

  "_allowlist_function_transition": attr.label(
      default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
  )

การเพิ่มทรานซิชันจะทำให้ขนาดของกราฟบิลด์พุ่งสูงขึ้น ซึ่งจะตั้งค่ารายการที่อนุญาตในแพ็กเกจที่คุณสามารถสร้างเป้าหมายของกฎนี้ได้ ค่าเริ่มต้นใน Codeblock ข้างต้น อนุญาตทุกรายการ แต่หากต้องการจำกัดผู้ที่ใช้กฎของคุณ คุณสามารถตั้งค่าแอตทริบิวต์ดังกล่าวให้ชี้ไปยังรายการที่อนุญาตที่คุณกำหนดเองได้ โปรดติดต่อ bazel-discuss@googlegroups.com หากคุณต้องการคำแนะนำหรือความช่วยเหลือ ในการทำความเข้าใจว่าการเปลี่ยนจะส่งผลต่อประสิทธิภาพของบิลด์อย่างไร

การให้คำจำกัดความ

การเปลี่ยนจะกำหนดการเปลี่ยนแปลงการกำหนดค่าระหว่างกฎต่างๆ เช่น คำขออย่าง "คอมไพล์ข้อกำหนดของฉันสำหรับ 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() เมื่อแนบเป็นการเปลี่ยนปลายทางขาเข้า attr จะไม่รวมแอตทริบิวต์ที่ใช้ตัวเลือกเพื่อแก้ไขค่า หากการเปลี่ยนผ่านขอบขาเข้าใน --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"]
)

นอกจากนี้ ยังตั้งค่าคีย์ที่กำหนดเองซึ่งฟังก์ชันการติดตั้งใช้งานกฎสามารถใช้เพื่ออ่านแต่ละรายการต่อไปนี้ได้

# 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 ที่ ได้แก่ ขอบขาเข้าและขอบขาออก ซึ่งหมายความว่ากฎสามารถเปลี่ยนการกำหนดค่าของตนเอง (การเปลี่ยนผ่านขอบขาเข้า) และเปลี่ยนการกำหนดค่าของรายการที่ตนพึ่งพา (การเปลี่ยนผ่านขอบขาออก)

หมายเหตุ: ขณะนี้ยังไม่มีวิธีแนบการเปลี่ยนของ Starlark กับกฎแบบเนทีฟ หากต้องการความช่วยเหลือ โปรดติดต่อ bazel-discuss@googlegroups.com เพื่อขอความช่วยเหลือในการหาวิธีแก้ปัญหา

การเปลี่ยนขอบขาเข้า

เปิดใช้งานการเปลี่ยนขอบขาเข้าได้โดยการแนบออบเจ็กต์ transition (สร้างโดย transition()) ลงในพารามิเตอร์ cfg ของ rule():

# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
    implementation = _impl,
    cfg = hot_chocolate_transition,
    ...

การเปลี่ยน EDGE ขาเข้าต้องเป็นการเปลี่ยนแบบ 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 เป็น Flag "expansion" ที่ขยายไปยัง Flag อื่นๆ

โดยพื้นฐานแล้ว --config อาจรวมแฟล็กที่ไม่ส่งผลต่อการกำหนดค่าบิลด์ เช่น --spawn_strategy การออกแบบของ Bazel ทำให้ไม่สามารถเชื่อมโยง Flag ดังกล่าวกับเป้าหมายแต่ละรายการได้ ซึ่งหมายความว่าไม่มีวิธีใช้ที่สอดคล้องกับการเปลี่ยน

วิธีแก้ปัญหาชั่วคราวคือคุณสามารถระบุ Flag ที่เป็นส่วนหนึ่งของการกําหนดค่าในการเปลี่ยนผ่านอย่างชัดเจน ซึ่งต้องคงการขยายตัวของ --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 เพื่ออ่าน Dep แต่ละรายการสำหรับแต่ละคีย์ได้ ดังนี้

# 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)
    })

ดูตัวอย่างที่สมบูรณ์ได้ที่นี่

การผสานรวมกับแพลตฟอร์มและ Toolchain

แฟล็กเนทีฟจำนวนมากในปัจจุบัน เช่น --cpu และ --crosstool_top เกี่ยวข้องกับความละเอียดของ Toolchain ในอนาคต การเปลี่ยนสถานะอย่างชัดเจนใน Flag ประเภทเหล่านี้มีแนวโน้มที่จะเปลี่ยนไปใช้การเปลี่ยนสถานะในแพลตฟอร์มเป้าหมาย

ข้อควรพิจารณาเกี่ยวกับหน่วยความจําและประสิทธิภาพ

การเพิ่มการเปลี่ยนและการกำหนดค่าใหม่ให้กับบิลด์จึงมีต้นทุนสูง เช่น กราฟบิลด์ที่ใหญ่ขึ้น กราฟบิลด์ที่เข้าใจได้น้อยลง และการสร้างที่ช้าลง คุณควรคำนึงถึงค่าใช้จ่ายเหล่านี้เมื่อพิจารณาใช้การเปลี่ยนในกฎการสร้าง ด้านล่างนี้คือตัวอย่างวิธีที่การเปลี่ยนอาจทําให้กราฟการสร้างเติบโตแบบทวีคูณ

กรณีศึกษาเกี่ยวกับบิลด์ที่ทำงานไม่ถูกต้อง

กราฟความสามารถในการปรับขนาด

รูปที่ 1 กราฟความสามารถในการปรับขนาดแสดงเป้าหมายระดับบนสุดและทรัพยากร Dependency ของเป้าหมาย

กราฟนี้แสดงเป้าหมายระดับบนสุด //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\) ใน \([1..n]\)

สมมติว่าคุณใช้ Flag --//foo:owner=<STRING> และ //pkg:i_b มีผล

depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"

กล่าวคือ //pkg:i_b จะเพิ่ม b ต่อท้ายค่าเดิมของ --owner สำหรับ deps ทั้งหมด

การดำเนินการนี้จะสร้างเป้าหมายที่กำหนดค่าไว้ต่อไปนี้

//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\)" สําหรับ \(b_i\) ทั้งหมดใน \(\{0,1\}\)

ทำให้กราฟบิลด์มีขนาดใหญ่กว่ากราฟเป้าหมายเป็นเท่าตัว โดยมีหน่วยความจำและประสิทธิภาพที่สอดคล้องกัน

TODO: เพิ่มกลยุทธ์ในการวัดผลและบรรเทาปัญหาเหล่านี้

อ่านเพิ่มเติม

ดูรายละเอียดเพิ่มเติมเกี่ยวกับการแก้ไขการกำหนดค่าบิลด์ได้ที่