Chuỗi công cụ

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

Trang này mô tả khung chuỗi công cụ. Đây là một cách để tác giả quy tắc phân tách logic quy tắc của họ khỏi việc lựa chọn các công cụ dựa trên nền tảng. Bạn nên đọc các trang quy tắcnền tảng trước khi tiếp tục. Trang này trình bày lý do tại sao cần có chuỗi công cụ, cách xác định và sử dụng các chuỗi đó, cũng như cách Bazel chọn một chuỗi công cụ phù hợp dựa trên các ràng buộc của nền tảng.

Động lực

Trước tiên, hãy xem xét các chuỗi công cụ được thiết kế để giải quyết vấn đề. Giả sử bạn đang viết các quy tắc để hỗ trợ ngôn ngữ lập trình "bar". Quy tắc bar_binary của bạn sẽ biên dịch các tệp *.bar bằng cách sử dụng trình biên dịch barc, một công cụ mà bản thân nó được tạo dưới dạng một mục tiêu khác trong không gian làm việc. Vì những người dùng viết mục tiêu bar_binary không cần phải chỉ định một phần phụ thuộc trên trình biên dịch, nên bạn hãy tạo phần phụ thuộc đó bằng cách thêm phần đó vào định nghĩa quy tắc dưới dạng một thuộc tính riêng tư.

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 hiện là phần phụ thuộc của mọi mục tiêu bar_binary, vì vậy, phần tử này sẽ được tạo trước bất kỳ mục tiêu bar_binary nào. Hàm có thể được truy cập bằng hàm triển khai của quy tắc giống như bất kỳ thuộc tính nào khác:

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),
    )
    ...

Vấn đề ở đây là nhãn của trình biên dịch được mã hóa cứng vào bar_binary, tuy nhiên các mục tiêu khác nhau có thể cần các trình biên dịch khác nhau tùy thuộc vào nền tảng chúng đang được xây dựng và nền tảng mà họ đang được xây dựng – được gọi là nền tảng mục tiêunền tảng thực thi tương ứng. Hơn nữa, tác giả quy tắc không nhất thiết thậm chí không biết tất cả các công cụ và nền tảng có sẵn, vì vậy không thể mã hoá cứng chúng trong định nghĩa của quy tắc.

Một giải pháp không kém phần lý tưởng là chuyển gánh nặng cho người dùng, bằng cách đặt thuộc tính _compiler ở chế độ không riêng tư. Sau đó, bạn có thể mã hoá cứng các mục tiêu riêng lẻ để tạo cho một nền tảng hoặc nền tảng khác.

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

Bạn có thể cải thiện giải pháp này bằng cách sử dụng select để chọn compiler dựa trên nền tảng:

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

Tuy nhiên, việc này rất tẻ nhạt và cần hỏi một chút về mọi người dùng bar_binary. Nếu bạn không sử dụng kiểu này nhất quán trong không gian làm việc, điều này sẽ dẫn đến việc tạo bản dựng hoạt động tốt trên một nền tảng nhưng không thành công khi mở rộng sang các kịch bản đa nền tảng. Nó cũng không giải quyết vấn đề thêm tính năng hỗ trợ cho các nền tảng và trình biên dịch mới mà không cần sửa đổi các quy tắc hoặc mục tiêu hiện có.

Khung chuỗi công cụ giải quyết vấn đề này bằng cách thêm một cấp gián tiếp. Về cơ bản, bạn khai báo rằng quy tắc của bạn có phần phụ thuộc trừu tượng trên một số thành viên của một nhóm mục tiêu (loại chuỗi công cụ) và Bazel sẽ tự động giải quyết vấn đề này cho một mục tiêu cụ thể (chuỗi công cụ) dựa trên các hạn chế hiện hành của nền tảng. Cả tác giả quy tắc lẫn tác giả mục tiêu đều không cần tập hợp đầy đủ các nền tảng và chuỗi công cụ có sẵn.

Viết quy tắc sử dụng chuỗi công cụ

Trong khung chuỗi công cụ, thay vì để các quy tắc phụ thuộc trực tiếp vào công cụ, các quy tắc này phụ thuộc vào loại chuỗi công cụ. Loại chuỗi công cụ là một mục tiêu đơn giản, đại diện cho một lớp các công cụ cùng đóng vai trò cho các nền tảng khác nhau. Ví dụ: bạn có thể khai báo một loại đại diện cho trình biên dịch thanh:

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

Định nghĩa quy tắc trong phần trước đã được sửa đổi để thay vì lấy trình biên dịch làm thuộc tính, nó khai báo rằng nó sử dụng chuỗi công cụ //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"],
)

Hàm triển khai hiện truy cập vào phần phụ thuộc này trong ctx.toolchains thay vì ctx.attr, sử dụng loại chuỗi công cụ làm khoá.

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"] trả về nhà cung cấp ToolchainInfo của bất kỳ mục tiêu nào mà Bazel đã giải quyết cho phần phụ thuộc chuỗi công cụ. Các trường của đối tượng ToolchainInfo được đặt bởi quy tắc của công cụ cơ bản; trong phần tiếp theo, quy tắc này được xác định sao cho có một trường barcinfo bọc đối tượng BarcInfo.

Quy trình phân giải chuỗi công cụ cho các mục tiêu của Bazel được mô tả dưới đây. Chỉ có mục tiêu của chuỗi công cụ được giải quyết mới là phần phụ thuộc của mục tiêu bar_binary, chứ không phải toàn bộ không gian của chuỗi công cụ ứng viên.

Chuỗi công cụ bắt buộc và không bắt buộc

Theo mặc định, khi một quy tắc thể hiện phần phụ thuộc loại chuỗi công cụ bằng nhãn trần (như trình bày ở trên), thì loại chuỗi công cụ sẽ được coi là bắt buộc. Nếu Bazel không thể tìm thấy chuỗi công cụ phù hợp (xem độ phân giải của Chuỗi công cụ bên dưới) cho loại chuỗi công cụ bắt buộc, thì đây là lỗi và phân tích sẽ tạm dừng.

Bạn cũng có thể khai báo phần phụ thuộc loại chuỗi công cụ không bắt buộc như sau:

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

Khi không thể phân giải loại chuỗi công cụ không bắt buộc, kết quả phân tích sẽ tiếp tục và ctx.toolchains["//bar_tools:toolchain_type"]None.

Hàm config_common.toolchain_type mặc định là bắt buộc.

Bạn có thể sử dụng các biểu mẫu sau:

  • Các loại chuỗi công cụ bắt buộc:
    • 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)]
  • Các loại chuỗi công cụ không bắt buộc:
    • 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),
    ],
)

Bạn cũng có thể kết hợp và khớp các biểu mẫu trong cùng một quy tắc. Tuy nhiên, nếu cùng một loại chuỗi công cụ được liệt kê nhiều lần, thì phiên bản sẽ nghiêm ngặt hơn, trong đó bắt buộc sẽ nghiêm ngặt hơn phiên bản không bắt buộc.

Viết các khía cạnh sử dụng chuỗi công cụ

Các khía cạnh có quyền truy cập vào cùng một API chuỗi công cụ như quy tắc: bạn có thể xác định các loại chuỗi công cụ bắt buộc, truy cập chuỗi công cụ thông qua ngữ cảnh và sử dụng các chuỗi đó để tạo hành động mới bằng chuỗi công cụ.

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

Xác định chuỗi công cụ

Để xác định một số chuỗi công cụ cho một loại chuỗi công cụ nhất định, bạn cần có 3 mục sau:

  1. Quy tắc cụ thể cho từng ngôn ngữ đại diện cho loại công cụ hoặc bộ công cụ. Theo quy ước, tên của quy tắc này được thêm hậu tố bằng "_toolchain".

    1. Lưu ý: Quy tắc \_toolchain không thể tạo bất kỳ hành động nào của bản dựng. Thay vào đó, công cụ này thu thập các cấu phần phần mềm từ các quy tắc khác và chuyển tiếp các cấu phần phần mềm đó đến quy tắc sử dụng chuỗi công cụ. Quy tắc đó chịu trách nhiệm tạo tất cả các hành động xây dựng.
  2. Một số mục tiêu của loại quy tắc này, đại diện cho các phiên bản của công cụ hoặc bộ công cụ cho các nền tảng khác nhau.

  3. Đối với mỗi mục tiêu như vậy, mục tiêu liên kết của quy tắc chung toolchain là cung cấp siêu dữ liệu mà khung công cụ sử dụng. Mục tiêu toolchain này cũng tham chiếu đến toolchain_type được liên kết với chuỗi công cụ này. Điều này có nghĩa là một quy tắc _toolchain nhất định có thể được liên kết với bất kỳ toolchain_type nào và chỉ trong một thực thể toolchain sử dụng quy tắc _toolchain này mà quy tắc đó được liên kết với toolchain_type.

Ví dụ về cách chạy của chúng ta dưới đây là định nghĩa cho quy tắc bar_toolchain. Ví dụ của chúng tôi chỉ có trình biên dịch, nhưng các công cụ khác như trình liên kết cũng có thể được nhóm lại bên dưới.

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(),
    },
)

Quy tắc này phải trả về một nhà cung cấp ToolchainInfo. Nhà cung cấp này sẽ trở thành đối tượng mà quy tắc sử dụng sẽ truy xuất bằng ctx.toolchains và nhãn của loại chuỗi công cụ. ToolchainInfo, giống như struct, có thể chứa các cặp giá trị trường tùy ý. Thông số kỹ thuật của chính xác những trường được thêm vào ToolchainInfo phải được ghi lại rõ ràng tại loại chuỗi công cụ. Trong ví dụ này, các giá trị được trả về trong đối tượng BarcInfo để sử dụng lại giản đồ được xác định ở trên; kiểu này có thể hữu ích cho việc xác thực và sử dụng lại mã.

Giờ đây, bạn có thể xác định mục tiêu cho các trình biên dịch barc cụ thể.

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

Cuối cùng, bạn tạo định nghĩa toolchain cho hai mục tiêu bar_toolchain. Các định nghĩa này liên kết các mục tiêu theo ngôn ngữ cụ thể với loại chuỗi công cụ và cung cấp thông tin ràng buộc cho Bazel biết khi chuỗi công cụ phù hợp với một nền tảng nhất định.

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

Việc sử dụng cú pháp đường dẫn tương đối ở trên cho thấy các định nghĩa này đều nằm trong cùng một gói, nhưng không có lý do gì để loại chuỗi công cụ, mục tiêu chuỗi công cụ dành riêng cho ngôn ngữ và mục tiêu định nghĩa toolchain không thể nằm trong các gói riêng biệt.

Hãy xem go_toolchain để biết ví dụ thực tế.

Chuỗi và cấu hình công cụ

Một câu hỏi quan trọng đối với các tác giả quy tắc là khi mục tiêu bar_toolchain được phân tích, cấu hình sẽ nhìn thấy gì và nên sử dụng chuyển đổi nào cho các phần phụ thuộc? Ví dụ trên sử dụng các thuộc tính chuỗi, nhưng điều gì sẽ xảy ra với một chuỗi công cụ phức tạp hơn phụ thuộc vào các mục tiêu khác trong kho lưu trữ Bazel?

Hãy xem phiên bản bar_toolchain phức tạp hơn:

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(),
    },
)

Việc sử dụng attr.label giống như đối với quy tắc chuẩn, nhưng ý nghĩa của thông số cfg hơi khác.

Phần phụ thuộc từ một mục tiêu (được gọi là "mẹ") với một chuỗi công cụ thông qua việc phân giải chuỗi công cụ sẽ sử dụng một quá trình chuyển đổi cấu hình đặc biệt gọi là "chuyển đổi chuỗi công cụ". Quá trình chuyển đổi chuỗi công cụ giữ nguyên cấu hình, ngoại trừ việc nó buộc nền tảng thực thi giống nhau đối với chuỗi công cụ giống như đối với nhà xuất bản mẹ (nếu không, độ phân giải chuỗi công cụ dành cho chuỗi công cụ có thể chọn bất kỳ nền tảng thực thi nào và không nhất thiết là giống như nền tảng gốc). Điều này cho phép mọi phần phụ thuộc exec của chuỗi công cụ được thực thi trong các thao tác xây dựng của thư mục mẹ. Mọi phần phụ thuộc của chuỗi công cụ sử dụng cfg = "target" (hoặc các phần không chỉ định cfg, vì "target" là mặc định) được tạo cho cùng một nền tảng mục tiêu giống như phần tử mẹ. Điều này cho phép các quy tắc chuỗi công cụ đóng góp cả hai thư viện (thuộc tính system_lib ở trên) và các công cụ (thuộc tính compiler) vào các quy tắc xây dựng cần các quy tắc đó. Các thư viện hệ thống được liên kết vào cấu phần phần mềm cuối cùng, vì vậy, bạn cần xây dựng cho cùng một nền tảng, trong khi trình biên dịch là một công cụ được gọi trong quá trình tạo bản dựng và cần có thể chạy trên nền tảng thực thi.

Đăng ký và xây dựng bằng chuỗi công cụ

Tại thời điểm này, tất cả các khối xây dựng đều được tập hợp và bạn chỉ cần cung cấp chuỗi công cụ cho quy trình phân giải của Bazel. Bạn có thể thực hiện việc này bằng cách đăng ký chuỗi công cụ, trong tệp WORKSPACE sử dụng register_toolchains(), hoặc bằng cách chuyển nhãn của chuỗi công cụ trên dòng lệnh bằng cách sử dụng cờ --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",
)

Giờ đây, khi bạn tạo một mục tiêu phụ thuộc vào loại chuỗi công cụ, một chuỗi công cụ phù hợp sẽ được chọn dựa trên nền tảng mục tiêu và thực thi.

# 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 sẽ thấy rằng //my_pkg:my_bar_binary đang được xây dựng bằng một nền tảng có @platforms//os:linux, do đó giải quyết vấn đề tham chiếu //bar_tools:toolchain_type thành //bar_tools:barc_linux_toolchain. Thao tác này sẽ tạo ra //bar_tools:barc_linux nhưng không xây dựng được //bar_tools:barc_windows.

Chuỗi công cụ phân giải

Đối với mỗi mục tiêu sử dụng chuỗi công cụ, quy trình phân giải chuỗi công cụ của Bazel sẽ xác định các phần phụ thuộc của chuỗi công cụ cụ thể của mục tiêu. Quy trình này sẽ nhập một tập hợp các loại chuỗi công cụ bắt buộc, nền tảng mục tiêu, danh sách các nền tảng thực thi có sẵn và danh sách các chuỗi công cụ có sẵn. Kết quả xuất ra là một chuỗi công cụ đã chọn cho mỗi loại chuỗi công cụ, cũng như một nền tảng thực thi đã chọn cho mục tiêu hiện tại.

Các nền tảng và chuỗi công cụ thực thi hiện có được thu thập từ tệp WORKSPACE thông qua register_execution_platformsregister_toolchains. Bạn cũng có thể chỉ định các nền tảng thực thi bổ sung và chuỗi công cụ trên dòng lệnh thông qua --extra_execution_platforms--extra_toolchains. Nền tảng lưu trữ sẽ tự động được bao gồm dưới dạng một nền tảng thực thi có sẵn. Các nền tảng và chuỗi công cụ có sẵn được theo dõi dưới dạng danh sách được sắp xếp theo thứ tự xác định, ưu tiên các mục trước đó trong danh sách.

Các bước giải quyết như sau.

  1. Mệnh đề target_compatible_with hoặc exec_compatible_with khớp với một nền tảng nếu, đối với mỗi constraint_value trong danh sách, nền tảng cũng có constraint_value đó (một cách rõ ràng hoặc mặc định).

    Nếu nền tảng có các constraint_value từ constraint_setting không được tham chiếu theo mệnh đề này, thì các nền tảng này sẽ không ảnh hưởng đến quá trình so khớp.

  2. Nếu mục tiêu đang được tạo sẽ chỉ định thuộc tính exec_compatible_with (hoặc định nghĩa quy tắc thì chỉ định đối số exec_compatible_with), danh sách nền tảng thực thi có sẵn sẽ được lọc để xóa mọi đối tượng không khớp với các quy tắc ràng buộc thực thi.

  3. Đối với mỗi nền tảng thực thi có sẵn, bạn liên kết từng loại chuỗi công cụ với chuỗi công cụ đầu tiên có sẵn (nếu có) tương thích với nền tảng thực thi này và nền tảng mục tiêu.

  4. Bất kỳ nền tảng thực thi nào không tìm thấy chuỗi công cụ bắt buộc tương thích cho một trong các loại chuỗi công cụ của nền tảng đó đều sẽ bị loại trừ. Trong số các nền tảng còn lại, nền tảng đầu tiên trở thành nền tảng thực thi của mục tiêu hiện tại, và các chuỗi công cụ liên kết (nếu có) sẽ trở thành phần phụ thuộc của mục tiêu.

Nền tảng thực thi đã chọn được dùng để chạy tất cả hành động mà mục tiêu tạo ra.

Trong trường hợp một mục tiêu có thể được xây dựng trong nhiều cấu hình (chẳng hạn như cho các CPU khác nhau) trong cùng một bản dựng, quy trình phân giải sẽ được áp dụng độc lập cho từng phiên bản của mục tiêu.

Nếu quy tắc sử dụng nhóm thực thi, mỗi nhóm thực thi sẽ thực hiện riêng biệt độ phân giải chuỗi công cụ, đồng thời mỗi nhóm có nền tảng thực thi và chuỗi công cụ riêng.

Gỡ lỗi chuỗi công cụ

Nếu bạn đang thêm tính năng hỗ trợ chuỗi công cụ vào một quy tắc hiện có, hãy sử dụng cờ --toolchain_resolution_debug=regex. Trong quá trình phân giải chuỗi công cụ, cờ cung cấp kết quả chi tiết cho các loại chuỗi công cụ hoặc tên mục tiêu khớp với biến biểu thức chính quy. Bạn có thể dùng .* để xuất tất cả thông tin. Bazel sẽ xuất tên của các chuỗi công cụ mà công cụ này kiểm tra và bỏ qua trong quá trình giải quyết.

Nếu bạn muốn xem phần phụ thuộc cquery nào từ độ phân giải chuỗi công cụ, hãy sử dụng cờ --transitions của 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