1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
|
---
layout: documentation
title: Toolchains
---
# Toolchains
This page describes the toolchain framework -- a way for rule authors to
decouple their rule logic from platform-based selection of tools. It is
recommended to read the
[rules](skylark/rules.html)
and
[platforms](platforms.html)
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
Let's first look at the problem toolchains are designed to solve. Suppose you
were 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 we'll make them strings instead. 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",
)
```
We can improve on this solution by using `select` to choose the `compiler`
[based on the platform](configurable-attributes.html):
```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
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, we 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](skylark/lib/platform_common.html#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 we'll define this rule 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.
### Writing aspects that use 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
To define some toolchains for a given toolchain type, we 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".
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`](be/platform.html#toolchain)
rule, to provide metadata used by the toolchain framework.
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. Here we have returned the
values wrapped in a `BarcInfo` object in order to reuse the schema defined
above; this style may be useful for validation and code reuse.
Now we 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, we 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.
## Registering and building with toolchains
At this point all the building blocks are assembled, and we just need to make
the toolchains available to Bazel's resolution procedure. This is done by
registering the toolchain, either in a `WORKSPACE` 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 we could have also written:
# "//bar_tools:all",
)
```
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
**Note:**
[Some Bazel rules](platforms-intro.html#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
`WORKSPACE` file via
[`register_execution_platforms`](skylark/lib/globals.html#register_execution_platforms)
and
[`register_toolchains`](skylark/lib/globals.html#register_toolchains).
Additional execution platforms and toolchains may also be specified on the
command line via
[`--extra_execution_platforms`](command-line-reference.html#flag--extra_execution_platforms)
and
[`--extra_toolchains`](command-line-reference.html#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 resolution steps are as follows.
1. A `target_compatible_with` or `exec_compatible_with` clause *matches* a
platform iff, 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](be/common-definitions.html#common.exec_compatible_with)
(or its rule definition specifies the
[`exec_compatible_with` argument](skylark/lib/globals.html#rule.exec_compatible_with)),
the list of available execution platforms is filtered to remove
any that do not match the execution constraints.
1. For each available execution platform, we 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 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 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.
## Debugging toolchains
When adding toolchain support to an existing rule, use the
`--toolchain_resolution_debug` flag to make toolchain resolution verbose. Bazel
will output names of toolchains it is checking and skipping during the
resolution process.
|