---
title: 'Toolchains'
---

This page describes the toolchain framework, which is a way for rule authors to
decouple their rule logic from platform-based selection of tools. It is
recommended to read the [rules](/versions/9.0.0/extending/rules) and [platforms](/versions/9.0.0/extending/platforms)
pages before continuing. This page covers why toolchains are needed, how to
define and use them, and how Bazel selects an appropriate toolchain based on
platform constraints.

## Motivation {#motivation}

Let's first look at the problem toolchains are designed to solve. Suppose you
are writing rules to support the "bar" programming language. Your `bar_binary`
rule would compile `*.bar` files using the `barc` compiler, a tool that itself
is built as another target in your workspace. Since users who write `bar_binary`
targets shouldn't have to specify a dependency on the compiler, you make it an
implicit dependency by adding it to the rule definition as a private attribute.

```python
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` is now a dependency of every `bar_binary` target, so
it'll be built before any `bar_binary` target. It can be accessed by the rule's
implementation function just like any other attribute:

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

The issue here is that the compiler's label is hardcoded into `bar_binary`, yet
different targets may need different compilers depending on what platform they
are being built for and what platform they are being built on -- called the
*target platform* and *execution platform*, respectively. Furthermore, the rule
author does not necessarily even know all the available tools and platforms, so
it is not feasible to hardcode them in the rule's definition.

A less-than-ideal solution would be to shift the burden onto users, by making
the `_compiler` attribute non-private. Then individual targets could be
hardcoded to build for one platform or another.

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

You can improve on this solution by using `select` to choose the `compiler`
[based on the platform](/versions/9.0.0/docs/configurable-attributes):

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

But this is tedious and a bit much to ask of every single `bar_binary` user.
If this style is not used consistently throughout the workspace, it leads to
builds that work fine on a single platform but fail when extended to
multi-platform scenarios. It also does not address the problem of adding support
for new platforms and compilers without modifying existing rules or targets.

The toolchain framework solves this problem by adding an extra level of
indirection. Essentially, you declare that your rule has an abstract dependency
on *some* member of a family of targets (a toolchain type), and Bazel
automatically resolves this to a particular target (a toolchain) based on the
applicable platform constraints. Neither the rule author nor the target author
need know the complete set of available platforms and toolchains.

## Writing rules that use toolchains {#writing-rules-toolchains}

Under the toolchain framework, instead of having rules depend directly on tools,
they instead depend on *toolchain types*. A toolchain type is a simple target
that represents a class of tools that serve the same role for different
platforms. For instance, you can declare a type that represents the bar
compiler:

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

The rule definition in the previous section is modified so that instead of
taking in the compiler as an attribute, it declares that it consumes a
`//bar_tools:toolchain_type` toolchain.

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

The implementation function now accesses this dependency under `ctx.toolchains`
instead of `ctx.attr`, using the toolchain type as the key.

```python
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"]` returns the
[`ToolchainInfo` provider](/versions/9.0.0/rules/lib/toplevel/platform_common#ToolchainInfo)
of whatever target Bazel resolved the toolchain dependency to. The fields of the
`ToolchainInfo` object are set by the underlying tool's rule; in the next
section, this rule is defined such that there is a `barcinfo` field that wraps
a `BarcInfo` object.

Bazel's procedure for resolving toolchains to targets is described
[below](#toolchain-resolution). Only the resolved toolchain target is actually
made a dependency of the `bar_binary` target, not the whole space of candidate
toolchains.

### Mandatory and Optional Toolchains {#optional-toolchains}

By default, when a rule expresses a toolchain type dependency using a bare label
(as shown above), the toolchain type is considered to be **mandatory**. If Bazel
is unable to find a matching toolchain (see
[Toolchain resolution](#toolchain-resolution) below) for a mandatory toolchain
type, this is an error and analysis halts.

It is possible instead to declare an **optional** toolchain type dependency, as
follows:

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

When an optional toolchain type cannot be resolved, analysis continues, and the
result of `ctx.toolchains["//bar_tools:toolchain_type"]` is `None`.

The [`config_common.toolchain_type`](/versions/9.0.0/rules/lib/toplevel/config_common#toolchain_type)
function defaults to mandatory.

The following forms can be used:

-  Mandatory toolchain types:
   -  `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)]`
- Optional toolchain types:
   -  `toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]`

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

You can mix and match forms in the same rule, also. However, if the same
toolchain type is listed multiple times, it will take the most strict version,
where mandatory is more strict than optional.

### Writing aspects that use toolchains {#writing-aspects-toolchains}

Aspects have access to the same toolchain API as rules: you can define required
toolchain types, access toolchains via the context, and use them to generate new
actions using the toolchain.

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

## Defining toolchains {#toolchain-definitions}

To define some toolchains for a given toolchain type, you need three things:

1. A language-specific rule representing the kind of tool or tool suite. By
   convention this rule's name is suffixed with "\_toolchain".

    1.  **Note:** The `\_toolchain` rule cannot create any build actions.
        Rather, it collects artifacts from other rules and forwards them to the
        rule that uses the toolchain. That rule is responsible for creating all
        build actions.

2. Several targets of this rule type, representing versions of the tool or tool
   suite for different platforms.

3. For each such target, an associated target of the generic
  [`toolchain`](/versions/9.0.0/reference/be/platforms-and-toolchains#toolchain)
   rule, to provide metadata used by the toolchain framework. This `toolchain`
   target also refers to the `toolchain_type` associated with this toolchain.
   This means that a given `_toolchain` rule could be associated with any
   `toolchain_type`, and that only in a `toolchain` instance that uses
   this `_toolchain` rule that the rule is associated with a `toolchain_type`.

For our running example, here's a definition for a `bar_toolchain` rule. Our
example has only a compiler, but other tools such as a linker could also be
grouped underneath it.

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

The rule must return a `ToolchainInfo` provider, which becomes the object that
the consuming rule retrieves using `ctx.toolchains` and the label of the
toolchain type. `ToolchainInfo`, like `struct`, can hold arbitrary field-value
pairs. The specification of exactly what fields are added to the `ToolchainInfo`
should be clearly documented at the toolchain type. In this example, the values
return wrapped in a `BarcInfo` object to reuse the schema defined above; this
style may be useful for validation and code reuse.

Now you can define targets for specific `barc` compilers.

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

Finally, you create `toolchain` definitions for the two `bar_toolchain` targets.
These definitions link the language-specific targets to the toolchain type and
provide the constraint information that tells Bazel when the toolchain is
appropriate for a given platform.

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

The use of relative path syntax above suggests these definitions are all in the
same package, but there's no reason the toolchain type, language-specific
toolchain targets, and `toolchain` definition targets can't all be in separate
packages.

See the [`go_toolchain`](https://github.com/bazelbuild/rules_go/blob/master/go/private/go_toolchain.bzl)
for a real-world example.

### Toolchains and configurations

An important question for rule authors is, when a `bar_toolchain` target is
analyzed, what [configuration](/versions/9.0.0/reference/glossary#configuration) does it see, and what transitions
should be used for dependencies? The example above uses string attributes, but
what would happen for a more complicated toolchain that depends on other targets
in the Bazel repository?

Let's see a more complex version of `bar_toolchain`:

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

The use of [`attr.label`](/versions/9.0.0/rules/lib/toplevel/attr#label) is the same as for a standard rule,
but the meaning of the `cfg` parameter is slightly different.

When a target (the "parent") depends on a toolchain via toolchain resolution,
Bazel applies a special configuration transition called the "toolchain transition".

In this transition Bazel keeps the overall configuration identical, but forces
the toolchain to use the same execution platform as the parent. Without this,
toolchain resolution could pick a different execution platform for the
toolchain, and the tools it provides might not be runnable for the parent's
actions.

This alignment of execution platforms guarantees that any dependencies of the
toolchain declared with `cfg = "exec"` are built so they can run where the
parent's actions run.

Dependencies declared with `cfg = "target"` (or which omit `cfg`, since
"target" is the default) are built for the parent's target platform. This lets
the toolchain contribute both runtime libraries and build-time tools.

In practice:

- `cfg = "exec"`: build artifacts intended to run during the build (e.g.
  the `compiler` tool) and therefore for the execution platform.
- `cfg = "target"`: build artifacts intended to be linked into or shipped with
  the final output (e.g. the `system_lib`) and therefore for the target
  platform.

The system libraries must be built for the same target platform as the final
artifact, while the compiler must be runnable on the execution platform.

### Selecting `exec` dependencies {#### Selecting `exec` dependencies}

For toolchain config rules, adding a `select()` to an attribute with `cfg = "exec"`
does **not** make the `select()` use the exec configuration.
It will still be resolved under the **target** configuration.

Assuming the target platform is not matching any of these constraints, the select for the compiler
will fail to analyze.

```python
bar_toolchain(
    name = "barc_omni",
    compiler = select({
        "@platforms//os:linux": Label(":linux_compiler"),
        "@platforms//os:windows": Label(":windows_compiler"),
        "//some/exec:platform": Label(":special_compiler"),
        },
        no_match_error = "No compiler for this execution platform",
    }),
    # <...>
)
```

A known workaround for this is to defer the evaluation behind another target,
e.g. [`alias()`](/versions/9.0.0/reference/be/general#alias).
This analyzes the `select` inside the alias under the exec configuration.

```python
alias(
    name = "host_specific_compiler",
    actual = select({
        "@platforms//os:linux": Label(":linux_compiler"),
        "@platforms//os:windows": Label(":windows_compiler"),
        "//some/exec:platform": Label(":special_compiler"),
        },
        no_match_error = "No compiler for this execution platform",
    ),
)

bar_toolchain(
    name = "barc_omni",
    compiler = ":host_specific_compiler",
    # <...>
)
```

## Registering and building with toolchains {#registering-building-toolchains}

At this point all the building blocks are assembled, and you just need to make
the toolchains available to Bazel's resolution procedure. This is done by
registering the toolchain, either in a `MODULE.bazel` file using
`register_toolchains()`, or by passing the toolchains' labels on the command
line using the `--extra_toolchains` flag.

```python
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/...",
)
```

When using target patterns to register toolchains, the order in which the
individual toolchains are registered is determined by the following rules:

* The toolchains defined in a subpackage of a package are registered before the
  toolchains defined in the package itself.
* Within a package, toolchains are registered in the lexicographical order of
  their names.

Now when you build a target that depends on a toolchain type, an appropriate
toolchain will be selected based on the target and execution platforms.

```python
# my_pkg/BUILD

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

bar_binary(
    name = "my_bar_binary",
    ...
)
```

```sh
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
```

Bazel will see that `//my_pkg:my_bar_binary` is being built with a platform that
has `@platforms//os:linux` and therefore resolve the
`//bar_tools:toolchain_type` reference to `//bar_tools:barc_linux_toolchain`.
This will end up building `//bar_tools:barc_linux` but not
`//bar_tools:barc_windows`.

## Toolchain resolution {#toolchain-resolution}

Note: [Some Bazel rules](/versions/9.0.0/concepts/platforms#status) do not yet support
toolchain resolution.

For each target that uses toolchains, Bazel's toolchain resolution procedure
determines the target's concrete toolchain dependencies. The procedure takes as
input a set of required toolchain types, the target platform, the list of
available execution platforms, and the list of available toolchains. Its outputs
are a selected toolchain for each toolchain type as well as a selected execution
platform for the current target.

The available execution platforms and toolchains are gathered from the
external dependency graph via
[`register_execution_platforms`](/versions/9.0.0/rules/lib/globals/module#register_execution_platforms)
and
[`register_toolchains`](/versions/9.0.0/rules/lib/globals/module#register_toolchains) calls in
`MODULE.bazel` files.
Additional execution platforms and toolchains may also be specified on the
command line via
[`--extra_execution_platforms`](/versions/9.0.0/reference/command-line-reference#flag--extra_execution_platforms)
and
[`--extra_toolchains`](/versions/9.0.0/reference/command-line-reference#flag--extra_toolchains).
The host platform is automatically included as an available execution platform.
Available platforms and toolchains are tracked as ordered lists for determinism,
with preference given to earlier items in the list.

The set of available toolchains, in priority order, is created from
`--extra_toolchains` and `register_toolchains`:

1. Toolchains registered using `--extra_toolchains` are added first. (Within
   these, the **last** toolchain has highest priority.)
2. Toolchains registered using `register_toolchains` in the transitive external
   dependency graph, in the following order: (Within these, the **first**
   mentioned toolchain has highest priority.)
  1. Toolchains registered by the root module (as in, the `MODULE.bazel` at the
     workspace root);
  2. Toolchains registered in the user's `WORKSPACE` file, including in any
     macros invoked from there;
  3. Toolchains registered by non-root modules (as in, dependencies specified by
     the root module, and their dependencies, and so forth);
  4. Toolchains registered in the "WORKSPACE suffix"; this is only used by
     certain native rules bundled with the Bazel installation.

**NOTE:** [Pseudo-targets like `:all`, `:*`, and `/...`](/versions/9.0.0/run/build#specifying-build-targets) are ordered by Bazel's package
loading mechanism, which uses a lexicographic ordering.

The resolution steps are as follows.

1. A `target_compatible_with` or `exec_compatible_with` clause *matches* a
   platform if, for each `constraint_value` in its list, the platform also has
   that `constraint_value` (either explicitly or as a default).

   If the platform has `constraint_value`s from `constraint_setting`s not
   referenced by the clause, these do not affect matching.

1. If the target being built specifies the
   [`exec_compatible_with` attribute](/versions/9.0.0/reference/be/common-definitions#common.exec_compatible_with)
   (or its rule definition specifies the
   [`exec_compatible_with` argument](/versions/9.0.0/rules/lib/globals/bzl#rule.exec_compatible_with)),
   the list of available execution platforms is filtered to remove
   any that do not match the execution constraints.

1. The list of available toolchains is filtered to remove any toolchains
   specifying `target_settings` that don't match the current configuration.

1. For each available execution platform, you associate each toolchain type with
   the first available toolchain, if any, that is compatible with this execution
   platform and the target platform.

1. Any execution platform that failed to find a compatible mandatory toolchain
   for one of its toolchain types is ruled out. Of the remaining platforms, the
   first one becomes the current target's execution platform, and its associated
   toolchains (if any) become dependencies of the target.

The chosen execution platform is used to run all actions that the target
generates.

In cases where the same target can be built in multiple configurations (such as
for different CPUs) within the same build, the resolution procedure is applied
independently to each version of the target.

If the rule uses [execution groups](/versions/9.0.0/extending/exec-groups), each execution
group performs toolchain resolution separately, and each has its own execution
platform and toolchains.

## Debugging toolchains {#debugging-toolchains}

If you are adding toolchain support to an existing rule, use the
`--toolchain_resolution_debug=regex` flag. During toolchain resolution, the flag
provides verbose output for toolchain types or target names that match the regex variable. You
can use `.*` to output all information. Bazel will output names of toolchains it
checks and skips during the resolution process.

For example, to debug toolchain selection for all actions created directly by
`//my:target`:

```sh
$ bazel build //my:all --toolchain_resolution_debug=//my:target
```

To debug toolchain selection for all actions over all build targets:

```sh
$ bazel build //my:all --toolchain_resolution_debug=.*
```

If you'd like to see which [`cquery`](/versions/9.0.0/query/cquery) dependencies are from toolchain
resolution, use `cquery`'s [`--transitions`](/versions/9.0.0/query/cquery#transitions) flag:

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