เชนเครื่องมือ

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

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

แรงจูงใจ

ก่อนอื่น มาดูที่ห่วงโซ่เครื่องมือโจทย์ที่ออกแบบมาเพื่อแก้ปัญหากัน สมมติว่าคุณกำลังเขียนกฎเพื่อรองรับภาษาโปรแกรมแบบ "bar" กฎ bar_binary จะรวบรวมไฟล์ *.bar โดยใช้คอมไพเลอร์ barc ซึ่งเป็นเครื่องมือที่สร้างขึ้นเองเป็นเป้าหมายอีกอย่างหนึ่งในพื้นที่ทำงานของคุณ เนื่องจากผู้ใช้ที่เขียนเป้าหมาย bar_binary ไม่ควรต้องระบุการขึ้นต่อกันของคอมไพเลอร์ คุณจึงต้องทำให้เป็นทรัพยากร Dependency โดยนัยโดยการเพิ่มพารามิเตอร์ดังกล่าวลงในคำจำกัดความของกฎเป็นแอตทริบิวต์ส่วนตัว

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        "_compiler": attr.label(
            default = "//bar_tools:barc_linux",  # the compiler running on linux
            providers = [BarcInfo],
        ),
    },
)

ขณะนี้ //bar_tools:barc_linux เป็นทรัพยากร Dependency ของเป้าหมาย bar_binary ทุกรายการแล้ว และจะสร้างขึ้นก่อนเป้าหมาย bar_binary ใดก็ตาม มิติข้อมูลนี้จะเข้าถึงได้ด้วยฟังก์ชันการใช้งานของกฎ เช่นเดียวกับแอตทริบิวต์อื่นๆ ต่อไปนี้

BarcInfo = provider(
    doc = "Information about how to invoke the barc compiler.",
    # In the real world, compiler_path and system_lib might hold File objects,
    # but for simplicity they are strings for this example. arch_flags is a list
    # of strings.
    fields = ["compiler_path", "system_lib", "arch_flags"],
)

def _bar_binary_impl(ctx):
    ...
    info = ctx.attr._compiler[BarcInfo]
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

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

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

bar_binary(
    name = "myprog_on_linux",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_linux",
)

bar_binary(
    name = "myprog_on_windows",
    srcs = ["mysrc.bar"],
    compiler = "//bar_tools:barc_windows",
)

คุณปรับปรุงโซลูชันนี้ได้โดยใช้ select เพื่อเลือก compiler ตามแพลตฟอร์ม

config_setting(
    name = "on_linux",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

config_setting(
    name = "on_windows",
    constraint_values = [
        "@platforms//os:windows",
    ],
)

bar_binary(
    name = "myprog",
    srcs = ["mysrc.bar"],
    compiler = select({
        ":on_linux": "//bar_tools:barc_linux",
        ":on_windows": "//bar_tools:barc_windows",
    }),
)

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

เฟรมเวิร์ก Toolchain ช่วยแก้ปัญหานี้ได้ด้วยการเพิ่มการบอกทางอ้อมอีกระดับ โดยพื้นฐานแล้ว คุณประกาศว่ากฎของคุณมีการพึ่งพากันอย่างเป็นนามธรรมกับสมาชิกบางคนของกลุ่มเป้าหมาย (ประเภทห่วงโซ่เครื่องมือ) และ Bazel จะแก้ไขปัญหานี้กับเป้าหมายเฉพาะ (ห่วงโซ่เครื่องมือ) โดยอัตโนมัติตามข้อจำกัดของแพลตฟอร์มที่เกี่ยวข้อง ทั้งผู้เขียนกฎและผู้เขียนเป้าหมายไม่จำเป็นต้องทราบชุดแพลตฟอร์มและชุดเครื่องมือทั้งหมดที่มีอยู่

กฎการเขียนที่ใช้ Toolchain

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

# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")

มีการแก้ไขคำจำกัดความของกฎในส่วนก่อนหน้าโดยให้ประกาศว่ามีการใช้เชนเครื่องมือ //bar_tools:toolchain_type แทนที่จะใช้คอมไพเลอร์เป็นแอตทริบิวต์

bar_binary = rule(
    implementation = _bar_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        ...
        # No `_compiler` attribute anymore.
    },
    toolchains = ["//bar_tools:toolchain_type"],
)

ตอนนี้ฟังก์ชันการใช้งานจะเข้าถึงทรัพยากร Dependency นี้ใน ctx.toolchains แทน ctx.attr โดยใช้ประเภท Toolchain เป็นคีย์

def _bar_binary_impl(ctx):
    ...
    info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
    # The rest is unchanged.
    command = "%s -l %s %s" % (
        info.compiler_path,
        info.system_lib,
        " ".join(info.arch_flags),
    )
    ...

ctx.toolchains["//bar_tools:toolchain_type"] แสดงผล ผู้ให้บริการ ToolchainInfo ของเป้าหมายที่ Bazel แก้ไขทรัพยากร Dependency ของ Toolchain ช่องของออบเจ็กต์ ToolchainInfo จะกำหนดโดยกฎของเครื่องมือที่สำคัญ ในส่วนถัดไป กฎนี้มีการกำหนดไว้ว่ามีช่อง barcinfo ที่รวมออบเจ็กต์ BarcInfo

ขั้นตอนการแก้ปัญหาของ Bazel สำหรับการแก้ไขปัญหาชุดเครื่องมือเป็นเป้าหมายได้อธิบายไว้ด้านล่าง เฉพาะเป้าหมาย Toolchain ที่ได้รับการแก้ไขเท่านั้นที่เป็นทรัพยากร Dependency ของเป้าหมาย bar_binary ไม่ใช่พื้นที่ทั้งหมดของ Toolchain ของผู้สมัคร

เชนเครื่องมือที่จำเป็นและที่ไม่บังคับ

โดยค่าเริ่มต้น เมื่อกฎแสดงทรัพยากร Dependency ของประเภท Toolchain โดยใช้ป้ายกำกับเปล่า (ดังที่แสดงด้านบน) จะถือว่าประเภท Toolchain เป็นบังคับ หาก Bazel ไม่พบห่วงโซ่เครื่องมือที่ตรงกัน (ดูการแก้ปัญหาของ Toolchain ด้านล่าง) สำหรับประเภทชุดเครื่องมือที่จำเป็น ข้อผิดพลาดและการวิเคราะห์จะหยุดทำงาน

คุณจะประกาศทรัพยากร Dependency ของประเภท Toolchain ไม่บังคับแทนได้ ดังนี้

bar_binary = rule(
    ...
    toolchains = [
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

เมื่อแก้ไขประเภทเชนเครื่องมือที่ไม่บังคับไม่ได้ การวิเคราะห์จะดำเนินต่อไป และผลลัพธ์ของ ctx.toolchains["//bar_tools:toolchain_type"] จะเป็น None

ฟังก์ชัน config_common.toolchain_type มีค่าเริ่มต้นเป็น "บังคับ"

โดยใช้แบบฟอร์มต่อไปนี้ได้

  • ประเภทห่วงโซ่เครื่องมือที่จำเป็น
    • toolchains = ["//bar_tools:toolchain_type"]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
  • ประเภทห่วงโซ่เครื่องมือ (ไม่บังคับ)
    • toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
    ...
    toolchains = [
        "//foo_tools:toolchain_type",
        config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
    ],
)

นอกจากนี้คุณยังผสมผสานแบบฟอร์มในกฎเดียวกันได้ อย่างไรก็ตาม หากมีการแสดงรายการ Toolchain ประเภทเดียวกันหลายครั้ง ระบบจะใช้เวอร์ชันที่เข้มงวดที่สุด ซึ่งข้อบังคับจะเข้มงวดกว่าตัวเลือก

ลักษณะการเขียนที่ใช้ Toolchain

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

bar_aspect = aspect(
    implementation = _bar_aspect_impl,
    attrs = {},
    toolchains = ['//bar_tools:toolchain_type'],
)

def _bar_aspect_impl(target, ctx):
  toolchain = ctx.toolchains['//bar_tools:toolchain_type']
  # Use the toolchain provider like in a rule.
  return []

การกำหนดเชนเครื่องมือ

หากต้องการกำหนดห่วงโซ่เครื่องมือสำหรับประเภท Toolchain หนึ่งๆ คุณต้องมี 3 สิ่งต่อไปนี้

  1. กฎเฉพาะภาษาซึ่งแสดงประเภทเครื่องมือหรือชุดเครื่องมือ โดยทั่วไปแล้ว ชื่อของกฎนี้ต่อท้ายด้วย "_toolchain"

    1. หมายเหตุ: กฎ \_toolchain ไม่สามารถสร้างการดำเนินการของบิลด์ใดๆ ได้ แต่จะรวบรวมอาร์ติแฟกต์จากกฎอื่นๆ และส่งต่อไปยังกฎที่ใช้ Toolchain กฎนั้นมีหน้าที่สร้าง การดำเนินการของบิลด์ทั้งหมด
  2. เป้าหมายหลายรายการของกฎประเภทนี้ โดยเป็นตัวแทนของเวอร์ชันของเครื่องมือหรือชุดเครื่องมือสำหรับแพลตฟอร์มต่างๆ

  3. สำหรับแต่ละเป้าหมายดังกล่าว ซึ่งเป็นเป้าหมายที่เกี่ยวข้องของกฎ toolchain ทั่วไปเพื่อระบุข้อมูลเมตาที่ใช้โดยเฟรมเวิร์ก Toolchain เป้าหมาย toolchain นี้หมายถึง toolchain_type ที่เชื่อมโยงกับ Toolchain นี้ด้วยเช่นกัน ซึ่งหมายความว่ากฎ _toolchain ที่ระบุสามารถเชื่อมโยงกับ toolchain_type ใดก็ได้ และเฉพาะในอินสแตนซ์ toolchain ที่ใช้กฎ _toolchain นี้ที่กฎดังกล่าวเชื่อมโยงกับ toolchain_type เท่านั้น

สำหรับตัวอย่างที่ทำงานอยู่ ต่อไปนี้เป็นคำจำกัดความของกฎ bar_toolchain ตัวอย่างของเรามีเพียงคอมไพเลอร์เท่านั้น แต่เครื่องมืออื่นๆ เช่น Linker ก็อาจจัดกลุ่มไว้ภายใต้เครื่องมือดังกล่าวได้เช่นกัน

def _bar_toolchain_impl(ctx):
    toolchain_info = platform_common.ToolchainInfo(
        barcinfo = BarcInfo(
            compiler_path = ctx.attr.compiler_path,
            system_lib = ctx.attr.system_lib,
            arch_flags = ctx.attr.arch_flags,
        ),
    )
    return [toolchain_info]

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler_path": attr.string(),
        "system_lib": attr.string(),
        "arch_flags": attr.string_list(),
    },
)

กฎต้องแสดงผลผู้ให้บริการ ToolchainInfo ซึ่งจะกลายเป็นออบเจ็กต์ที่กฎการใช้จะเรียกโดยใช้ ctx.toolchains และป้ายกำกับของประเภท Toolchain ToolchainInfo เช่นเดียวกับ struct จะมีคู่ฟิลด์-ค่าที่กำหนดเองได้ คุณควรระบุช่องต่างๆ ที่จะเพิ่มลงใน ToolchainInfo ไว้อย่างชัดเจนในประเภท Toolchain ในตัวอย่างนี้ ค่าจะแสดงผลที่รวมไว้ในออบเจ็กต์ BarcInfo เพื่อใช้สคีมาที่กำหนดไว้ข้างต้นซ้ำ สไตล์นี้อาจมีประโยชน์สำหรับการตรวจสอบความถูกต้องและนำโค้ดมาใช้ซ้ำ

ตอนนี้คุณสามารถกำหนดเป้าหมายสำหรับคอมไพเลอร์ barc ที่ต้องการได้

bar_toolchain(
    name = "barc_linux",
    arch_flags = [
        "--arch=Linux",
        "--debug_everything",
    ],
    compiler_path = "/path/to/barc/on/linux",
    system_lib = "/usr/lib/libbarc.so",
)

bar_toolchain(
    name = "barc_windows",
    arch_flags = [
        "--arch=Windows",
        # Different flags, no debug support on windows.
    ],
    compiler_path = "C:\\path\\on\\windows\\barc.exe",
    system_lib = "C:\\path\\on\\windows\\barclib.dll",
)

สุดท้าย คุณสร้างคําจํากัดความ toolchain สําหรับเป้าหมาย bar_toolchain ทั้ง 2 รายการ คำจำกัดความเหล่านี้จะลิงก์เป้าหมายเฉพาะภาษากับประเภทเชนเครื่องมือและให้ข้อมูลข้อจำกัดที่แจ้งให้ Bazel เมื่อเชนเครื่องมือเหมาะสมกับแพลตฟอร์มหนึ่งๆ

toolchain(
    name = "barc_linux_toolchain",
    exec_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_linux",
    toolchain_type = ":toolchain_type",
)

toolchain(
    name = "barc_windows_toolchain",
    exec_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    target_compatible_with = [
        "@platforms//os:windows",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":barc_windows",
    toolchain_type = ":toolchain_type",
)

การใช้ไวยากรณ์เส้นทางแบบสัมพัทธ์ข้างต้นแสดงให้เห็นว่าคำจำกัดความเหล่านี้อยู่ในแพ็กเกจเดียวกันทั้งหมด แต่ไม่มีเหตุผลที่ประเภท Toolchain เป้าหมาย Toolchain เฉพาะภาษา และเป้าหมายคำจำกัดความ toolchain จะอยู่ในแพ็กเกจแยกกันไม่ได้

ดู go_toolchain สำหรับตัวอย่างการใช้งานจริง

เชนเครื่องมือและการกำหนดค่า

คำถามสำคัญสำหรับผู้จัดทำกฎคือ เมื่อวิเคราะห์เป้าหมายbar_toolchain แล้วจะเห็นการกำหนดค่าอะไรบ้าง และควรใช้การเปลี่ยนแบบใดสำหรับทรัพยากร Dependency ตัวอย่างด้านบนใช้แอตทริบิวต์สตริง แต่จะเกิดอะไรขึ้นกับ Toolchain ที่ซับซ้อนขึ้นซึ่งขึ้นอยู่กับเป้าหมายอื่นๆ ในที่เก็บ Bazel

มาดูเวอร์ชันที่ซับซ้อนมากขึ้นของ bar_toolchain กัน

def _bar_toolchain_impl(ctx):
    # The implementation is mostly the same as above, so skipping.
    pass

bar_toolchain = rule(
    implementation = _bar_toolchain_impl,
    attrs = {
        "compiler": attr.label(
            executable = True,
            mandatory = True,
            cfg = "exec",
        ),
        "system_lib": attr.label(
            mandatory = True,
            cfg = "target",
        ),
        "arch_flags": attr.string_list(),
    },
)

การใช้ attr.label นั้นเหมือนกับกฎมาตรฐาน แต่ความหมายของพารามิเตอร์ cfg ต่างกันเล็กน้อย

ทรัพยากร Dependency จากเป้าหมาย (เรียกว่า "หลัก") ไปยังห่วงโซ่เครื่องมือผ่านการแก้ไขปัญหาของ Toolchain จะใช้การเปลี่ยนการกำหนดค่าพิเศษที่เรียกว่า "การเปลี่ยน Toolchain" การเปลี่ยน Toolchain คงการกำหนดค่าไว้เหมือนเดิมยกเว้นแต่จะบังคับให้แพลตฟอร์มการดำเนินการใน Toolchain เหมือนกันเช่นเดียวกับการดำเนินการระดับบนสุด (มิฉะนั้น การแก้ไข Toolchain สำหรับ Toolchain สามารถเลือกแพลตฟอร์มการดำเนินการได้และอาจไม่เหมือนกับเวอร์ชันหลัก) ซึ่งจะทำให้ทรัพยากร Dependency ของ exec ของ Toolchain เรียกใช้การดำเนินการของบิลด์ระดับบนสุดได้ด้วย ทรัพยากร Dependency ของ Toolchain ที่ใช้ cfg = "target" (หรือไม่ได้ระบุ cfg เนื่องจาก "target" เป็นค่าเริ่มต้น) สร้างขึ้นสำหรับแพลตฟอร์มเป้าหมายเดียวกันกับแพลตฟอร์มหลัก ซึ่งจะช่วยให้กฎ Toolchain มีทั้งไลบรารี (แอตทริบิวต์ system_lib ด้านบน) และเครื่องมือ (แอตทริบิวต์ compiler) ให้กับกฎบิลด์ที่ต้องใช้ ไลบรารีระบบจะลิงก์กับอาร์ติแฟกต์สุดท้าย และจำเป็นต้องสร้างขึ้นสำหรับแพลตฟอร์มเดียวกัน ในขณะที่คอมไพเลอร์เป็นเครื่องมือที่เรียกใช้ระหว่างบิลด์และจำเป็นต้องเรียกใช้บนแพลตฟอร์มการดำเนินการ

การลงทะเบียนและสร้างด้วย Toolchain

ณ จุดนี้ระบบจะประกอบองค์ประกอบพื้นฐานทั้งหมด และคุณเพียงแค่ต้องทำให้เชนเครื่องมือพร้อมใช้งานในขั้นตอนการแก้ปัญหาของ Bazel ซึ่งทำได้ด้วยการลงทะเบียน Toolchain ทั้งในไฟล์ MODULE.bazel โดยใช้ register_toolchains() หรือส่งป้ายกำกับของ Toolchains ในบรรทัดคำสั่งโดยใช้แฟล็ก --extra_toolchains

register_toolchains(
    "//bar_tools:barc_linux_toolchain",
    "//bar_tools:barc_windows_toolchain",
    # Target patterns are also permitted, so you could have also written:
    # "//bar_tools:all",
    # or even
    # "//bar_tools/...",
)

เมื่อใช้รูปแบบเป้าหมายในการลงทะเบียนห่วงโซ่เครื่องมือ ลำดับการลงทะเบียนเชนเครื่องมือแต่ละรายการจะกำหนดโดยกฎต่อไปนี้

  • เชนเครื่องมือที่กำหนดไว้ในแพ็กเกจย่อยของแพ็กเกจจะได้รับการลงทะเบียนก่อนเครื่องมือเชนที่กำหนดไว้ในแพ็กเกจ
  • ภายในแพ็กเกจ เชนเครื่องมือจะได้รับการบันทึกตามลำดับพจนานุกรมของชื่อ

ในตอนนี้เมื่อคุณสร้างเป้าหมายที่ขึ้นอยู่กับประเภท Toolchain แล้ว ระบบจะเลือก Toolchain ที่เหมาะสมตามแพลตฟอร์มเป้าหมายและการดำเนินการ

# my_pkg/BUILD

platform(
    name = "my_target_platform",
    constraint_values = [
        "@platforms//os:linux",
    ],
)

bar_binary(
    name = "my_bar_binary",
    ...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform

Bazel จะเห็นว่า //my_pkg:my_bar_binary สร้างขึ้นด้วยแพลตฟอร์มที่มี @platforms//os:linux ดังนั้นจึงแก้ไขการอ้างอิง //bar_tools:toolchain_type เป็น //bar_tools:barc_linux_toolchain ซึ่งท้ายที่สุดแล้วจะสร้าง //bar_tools:barc_linux แต่ไม่สร้าง //bar_tools:barc_windows

ความละเอียดของ Toolchain

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

แพลตฟอร์มการดำเนินการและเชนเครื่องมือที่พร้อมใช้งานจะรวบรวมจากกราฟการอ้างอิงภายนอกผ่าน register_execution_platforms และ register_toolchains การเรียกใช้ในไฟล์ MODULE.bazel อาจระบุแพลตฟอร์มการดำเนินการและเชนเครื่องมือเพิ่มเติมในบรรทัดคำสั่งผ่าน --extra_execution_platforms และ --extra_toolchains ได้ด้วย แพลตฟอร์มโฮสต์จะรวมอยู่ในแพลตฟอร์มการดำเนินการที่ใช้งานได้โดยอัตโนมัติ แพลตฟอร์มและห่วงโซ่เครื่องมือที่ใช้ได้จะมีการติดตามเป็นรายการตามลำดับสำหรับการตัดสินใจเชิงกำหนด โดยให้ความสำคัญกับรายการก่อนหน้าในรายการ

ชุดของเชนเครื่องมือที่ใช้ได้จะสร้างขึ้นจาก --extra_toolchains และ register_toolchains ตามลำดับความสำคัญ

  1. มีการเพิ่มเชนเครื่องมือที่ลงทะเบียนโดยใช้ --extra_toolchains ก่อน (ภายในตัวเลือกเหล่านี้ เชนเครื่องมือสุดท้ายมีลำดับความสำคัญสูงสุด)
  2. เชนเครื่องมือที่ลงทะเบียนโดยใช้ register_toolchains ในกราฟการอ้างอิงภายนอกแบบทรานซิทีฟตามลำดับต่อไปนี้ (ในเชนเครื่องมือที่พูดถึงจากรายการแรกจะมีลำดับความสำคัญสูงสุด)
    1. เชนเครื่องมือที่ลงทะเบียนโดยโมดูลรูท (เช่น MODULE.bazel ที่รูทของพื้นที่ทำงาน)
    2. เชนเครื่องมือที่ลงทะเบียนไว้ในไฟล์ WORKSPACE ของผู้ใช้ รวมถึงในมาโครที่เรียกใช้จากที่นั่น
    3. เชนเครื่องมือที่ลงทะเบียนโดยโมดูลที่ไม่ใช่รูท (เช่น ทรัพยากร Dependency ที่ระบุโดยโมดูลรูท และทรัพยากร Dependency ของเชน ฯลฯ)
    4. เชนเครื่องมือที่ลงทะเบียนไว้ใน "คำต่อท้าย WORKSPACE" ซึ่งจะใช้โดยกฎเนทีฟบางข้อที่รวมกับการติดตั้ง Bazel เท่านั้น

หมายเหตุ: เป้าหมายที่ไม่ระบุตัวบุคคล เช่น :all, :* และ /... เรียงลำดับตามกลไกการโหลดแพ็กเกจของ Bazel ซึ่งใช้ลำดับแบบพจนานุกรม

ขั้นตอนการแก้ปัญหามีดังนี้

  1. วรรค target_compatible_with หรือ exec_compatible_with จะตรงกับแพลตฟอร์มก็ต่อเมื่อสำหรับ constraint_value แต่ละรายการในรายการ แพลตฟอร์มมี constraint_value นั้นเช่นกัน (โดยชัดแจ้งหรือเป็นค่าเริ่มต้น)

    หากแพลตฟอร์มมี constraint_value จาก constraint_setting ที่ไม่ได้อ้างอิงโดยอนุประโยค ค่าเหล่านี้จะไม่ส่งผลต่อการจับคู่

  2. หากเป้าหมายที่สร้างระบุแอตทริบิวต์ exec_compatible_with (หรือคำจำกัดความของกฎระบุอาร์กิวเมนต์ exec_compatible_with) ระบบจะกรองรายการแพลตฟอร์มการดำเนินการที่ใช้ได้เพื่อนำรายการที่ไม่ตรงกับข้อจำกัดการดำเนินการออก

  3. ระบบจะกรองรายการเชนเครื่องมือที่ใช้ได้เพื่อนำเชนเครื่องมือที่ระบุ target_settings ออก ซึ่งไม่ตรงกับการกำหนดค่าปัจจุบัน

  4. สำหรับแพลตฟอร์มการดำเนินการที่ใช้ได้แต่ละแพลตฟอร์ม คุณต้องเชื่อมโยง Toolchain แต่ละประเภทกับ Toolchain แรกที่พร้อมใช้งาน (หากมี) ซึ่งเข้ากันได้กับแพลตฟอร์มการดำเนินการนี้และแพลตฟอร์มเป้าหมาย (หากมี)

  5. โดยจะตัดแพลตฟอร์มการดำเนินการใดๆ ที่ไม่พบเครื่องมือที่จำเป็นซึ่งใช้งานร่วมกันได้กับประเภท Toolchain จากแพลตฟอร์มที่เหลือ แพลตฟอร์มแรกจะกลายเป็นแพลตฟอร์มการดำเนินการของเป้าหมายปัจจุบัน และ Toolchain ที่เกี่ยวข้อง (หากมี) จะกลายเป็นทรัพยากร Dependency ของเป้าหมาย

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

ในกรณีที่สร้างเป้าหมายเดียวกันได้ในการกำหนดค่าหลายรายการ (เช่น สำหรับ CPU ที่ต่างกัน) ภายในบิลด์เดียวกัน ระบบจะใช้ขั้นตอนการแก้ปัญหาโดยอิสระกับเป้าหมายแต่ละเวอร์ชัน

หากกฎใช้กลุ่มการดำเนินการ กลุ่มการดำเนินการแต่ละกลุ่มจะดำเนินการแก้ปัญหา Toolchain แยกกัน และแต่ละกลุ่มจะมีแพลตฟอร์มการดำเนินการและ Toolchain ของตนเอง

การแก้ไขข้อบกพร่องห่วงโซ่เครื่องมือ

หากคุณจะเพิ่มการสนับสนุนเชนเครื่องมือลงในกฎที่มีอยู่ ให้ใช้แฟล็ก --toolchain_resolution_debug=regex ในระหว่างการแก้ไข Toolchain แฟล็กจะแสดงผลแบบละเอียดสำหรับประเภท Toolchain หรือชื่อเป้าหมายที่ตรงกับตัวแปรนิพจน์ทั่วไป คุณสามารถใช้ .* เพื่อแสดงข้อมูลทั้งหมด Bazel จะแสดงชื่อของห่วงโซ่เครื่องมือที่ตรวจสอบและข้ามในขั้นตอนการแก้ปัญหา

หากต้องการดูทรัพยากร Dependency ของ cquery ที่มาจากการแก้ไขเครื่องมือเชน ให้ใช้แฟล็ก --transitions ของ cquery ดังนี้

# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)

# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
  [toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211