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

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

แรงจูงใจ

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

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

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

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

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

การกำหนดกฎในส่วนก่อนหน้าได้รับการแก้ไขเพื่อให้กฎประกาศว่าใช้ Toolchain //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 เป็นเป้าหมายมีอธิบายไว้ ด้านล่าง เฉพาะเป้าหมาย Toolchain ที่แก้ปัญหาแล้วเท่านั้นที่จะกลายเป็นทรัพยากร Dependency ของเป้าหมาย bar_binary ไม่ใช่พื้นที่ทั้งหมดของ Toolchain ที่เป็นไปได้

Toolchain ที่บังคับและไม่บังคับ

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

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

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

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

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

คุณสามารถใช้รูปแบบต่อไปนี้

  • ประเภท Toolchain ที่บังคับ
    • 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)]
  • ประเภท Toolchain แบบไม่บังคับ
    • 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 เดียวกันหลายครั้ง ระบบจะใช้เวอร์ชันที่เข้มงวดที่สุด โดยที่บังคับจะเข้มงวดกว่าไม่บังคับ

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

Aspect มีสิทธิ์เข้าถึง API Toolchain เดียวกันกับกฎ โดยคุณสามารถกำหนดประเภท 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

หากต้องการกำหนด Toolchain บางรายการสำหรับประเภท 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 รายการ การกำหนดเหล่านี้จะลิงก์เป้าหมายเฉพาะภาษาเข้ากับประเภท Toolchain และให้ข้อมูลข้อจำกัดที่บอก Bazel ว่า Toolchain เหมาะสมกับแพลตฟอร์มใด

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

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 สำหรับ Toolchain อาจเลือกแพลตฟอร์มการดำเนินการใดก็ได้ และไม่จำเป็นต้องเหมือนกับรายการหลัก) การดำเนินการนี้ช่วยให้ทรัพยากร Dependency exec ของ Toolchain สามารถปฏิบัติการได้สำหรับการดำเนินการสร้างของรายการหลักด้วย ทรัพยากร Dependency ใดๆ ของ Toolchain ที่ใช้ cfg = "target" (หรือไม่ได้ระบุ cfg เนื่องจาก "target" เป็นค่าเริ่มต้น) จะ สร้างขึ้นสำหรับแพลตฟอร์มเป้าหมายเดียวกันกับรายการหลัก การดำเนินการนี้ช่วยให้กฎ Toolchain สามารถมีส่วนร่วมทั้งไลบรารี (แอตทริบิวต์ system_lib ด้านบน) และเครื่องมือ (แอตทริบิวต์ compiler) ในกฎการสร้างที่จำเป็น ระบบจะลิงก์ไลบรารีของระบบเข้ากับอาร์ติแฟกต์สุดท้าย ดังนั้นจึงต้องสร้างขึ้นสำหรับแพลตฟอร์มเดียวกัน ในขณะที่คอมไพเลอร์เป็นเครื่องมือที่เรียกใช้ระหว่างการบิลด์ และต้องสามารถทำงานบนแพลตฟอร์มการดำเนินการได้

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

ตอนนี้เราได้ประกอบองค์ประกอบที่ใช้สร้างสรรค์ทั้งหมดเข้าด้วยกันแล้ว และคุณเพียงแค่ต้องทำให้ Toolchain พร้อมใช้งานสำหรับขั้นตอนการแก้ปัญหาของ Bazel โดยลงทะเบียน Toolchain ในไฟล์ MODULE.bazel โดยใช้ register_toolchains() หรือส่งป้ายกำกับของ Toolchain ในบรรทัดคำสั่งโดยใช้แฟล็ก --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 แต่ละรายการจะกำหนดโดยกฎต่อไปนี้

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

ตอนนี้เมื่อคุณสร้างเป้าหมายที่ขึ้นอยู่กับประเภท 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

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

ระบบจะรวบรวมแพลตฟอร์มการดำเนินการและ Toolchain ที่ใช้ได้จาก กราฟทรัพยากร Dependency ภายนอกผ่านการเรียก register_execution_platforms และ register_toolchains ในไฟล์ MODULE.bazelfiles. Additional execution platforms and toolchains may also be specified on the command line via [--extra_execution_platforms](/versions/7.6.0/reference/command-line-reference#flag--extra_execution_platforms) and [--extra_toolchains`](/versions/7.6.0/reference/command-line-reference#flag--extra_toolchains) ระบบจะรวมแพลตฟอร์มโฮสต์เป็นแพลตฟอร์มการดำเนินการที่ใช้ได้โดยอัตโนมัติ ระบบจะติดตามแพลตฟอร์มและ Toolchain ที่ใช้ได้เป็นรายการที่เรียงลำดับเพื่อความแน่นอน โดยจะให้ความสำคัญกับรายการก่อนหน้าในรายการ

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

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

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

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

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

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

การแก้ไขข้อบกพร่องของ Toolchain

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

หากต้องการดูว่าทรัพยากร Dependency cquery รายการใดมาจากการแก้ปัญหา Toolchain ให้ใช้ cquery's --transitions ดังนี้

# 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