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

รายงานปัญหา ดูซอร์สโค้ด รุ่น Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

แรงจูงใจ

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

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 จะเป็นทรัพยากรที่ต้องพึ่งพาของเป้าหมาย bar_binary ทั้งหมด ดังนั้นระบบจะสร้าง //bar_tools:barc_linux ก่อนเป้าหมาย 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 ทุกคน หากไม่ได้ใช้สไตล์นี้อย่างสอดคล้องกันทั่วทั้งเวิร์กスペース จะทำให้บิลด์ทํางานได้ดีในแพลตฟอร์มเดียว แต่ใช้งานไม่ได้เมื่อขยายไปใช้ในสถานการณ์แบบหลายแพลตฟอร์ม นอกจากนี้ ก็ไม่ได้ช่วยแก้ปัญหาในการเพิ่มการรองรับแพลตฟอร์มและคอมไพเลอร์ใหม่โดยไม่แก้ไขกฎหรือเป้าหมายที่มีอยู่

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

การเขียนกฎที่ใช้ชุดเครื่องมือ

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

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

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

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 providerของเป้าหมายใดก็ตามที่ Bazel แก้ปัญหาการพึ่งพาใน Toolchain ช่องของออบเจ็กต์ ToolchainInfo กำหนดโดยกฎของเครื่องมือพื้นฐาน ส่วนในหัวข้อถัดไป กฎนี้จะกำหนดให้มีช่อง barcinfo ที่รวมออบเจ็กต์ BarcInfo

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

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

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

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

องค์ประกอบการเขียนที่ใช้ชุดเครื่องมือ

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

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 []

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

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

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

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

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

ต่อไปนี้คือคําจํากัดความของกฎ 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 และป้ายกำกับของประเภทเครื่องมือ ToolchainInfo เช่นเดียวกับ struct สามารถเก็บคู่ฟิลด์-ค่าแบบไม่จำกัด ข้อกำหนดของช่องที่จะเพิ่มลงใน ToolchainInfo ควรมีการบันทึกไว้อย่างชัดเจนในประเภทเครื่องมือทางเทคนิค ในตัวอย่างนี้ ค่าที่แสดงผลจะรวมอยู่ในออบเจ็กต์ 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 ทั้งหมดจะอยู่ในแพ็กเกจแยกต่างหากไม่ได้

ดูตัวอย่างการใช้งานจริงได้ที่ go_toolchain

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

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

การลงทะเบียนและการสร้างด้วยชุดเครื่องมือ

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

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

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

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

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

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

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

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

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

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

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

เครื่องมือแก้ไขข้อบกพร่อง

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

หากต้องการดูว่า cquery ใดบ้างที่มาจากเครื่องมือทางเทคนิค ให้ใช้ Flag --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