| --- |
| title: 'Platforms' |
| --- |
| |
| |
| |
| ## Intro |
| |
| Bazel can build and test code on a variety of hardware, operating systems, and |
| system configurations. This can involve different versions of build tools like |
| linkers and compilers. To help manage this complexity, Bazel has the concepts |
| of *constraints* and *platforms*. |
| |
| A *constraint* is a distinguishing property of a build or production machine. |
| Common constraints are CPU architecture, presence or absence of a GPU, or |
| version of a locally installed compiler. But constraints can be *anything* that |
| meaningfully distinguishes machines when orchestrating build work. |
| |
| A *platform* is a collection of constraints that specifies a complete machine. |
| Bazel uses this concept to let developers choose which machines they want to |
| build for, which machines should run compile and test actions, and which |
| [toolchains](/extending/toolchains) build actions should compile with. |
| |
| Developers can also use constraints to [select](/docs/configurable-attributes) |
| custom properties or dependencies on their build rules. For example: "*use |
| `src_arm.cc` when the build targets an Arm machine*". |
| |
| ## Platform types |
| |
| Bazel recognizes three roles a platform may play: |
| |
| * **Host** - The platform on which Bazel itself runs. |
| * **Execution** - A platform which runs compile actions to produce build outputs. |
| * **Target** - A platform the code being built should run on. |
| |
| Note: Builds only have one host platform, but often have multiple execution and |
| target platforms. For example, both remote Linux CI machines and developer Macs |
| may run build actions. And mobile apps have code for multiple phone types and |
| hardware extensions. |
| |
| Builds generally have three kinds of relationships to platforms: |
| |
| * **Single-platform builds** - Host, execution, and target platforms are the |
| same. For example, building on a developer machine without remote execution, |
| then running the built binary on the same machine. |
| |
| * **Cross-compilation builds** - Host and execution platforms are the same, |
| but the target platform is different. For example, building an iOS app on a |
| Macbook Pro without remote execution. |
| |
| * **Multi-platform builds** - Host, execution, and target platforms are all |
| different. For example, building an iOS app on a Macbook Pro and using remote |
| Linux machines to compile C++ actions that don't need Xcode. |
| |
| ## Specifying platforms |
| |
| The most common way for developers to use platforms is to specify desired |
| target machines with the `--platforms` flag: |
| |
| ```shell |
| $ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86 |
| ``` |
| |
| Organizations generally maintain their own platform definitions because build |
| machine setups vary between organizations. |
| |
| When `--platforms` isn't set, it defaults to `@platforms//host`. This is |
| specially defined to auto-detect the host machine's OS and CPU properties so |
| builds target the same machine Bazel runs on. Build rules can |
| [select](/docs/configurable-attributes) on these properties with the |
| [@platforms/os](https://github.com/bazelbuild/platforms/blob/main/os/BUILD) and |
| [@platforms/cpu](https://github.com/bazelbuild/platforms/blob/main/cpu/BUILD) |
| constraints. |
| |
| ## Generally useful constraints and platforms {:#useful-constraints-platforms} |
| |
| To keep the ecosystem consistent, Bazel team maintains a repository with |
| constraint definitions for the most popular CPU architectures and operating |
| systems. These are all defined in |
| [https://github.com/bazelbuild/platforms](https://github.com/bazelbuild/platforms){: .external}. |
| |
| Bazel ships with the following special platform definition: |
| `@platforms//host` (aliased as `@bazel_tools//tools:host_platform`). This |
| auto-detects the OS and CPU properties of the machine Bazel runs on. |
| |
| ## Defining constraints {:#constraints} |
| |
| Constraints are modeled with the [`constraint_setting`][constraint_setting] and |
| [`constraint_value`][constraint_value] build rules. |
| |
| [constraint_setting]: /reference/be/platforms-and-toolchains#constraint_setting |
| [constraint_value]: /reference/be/platforms-and-toolchains#constraint_value |
| |
| `constraint_setting` declares a type of property. For example: |
| |
| ```python |
| constraint_setting(name = "cpu") |
| ``` |
| |
| `constraint_value` declares a possible value for that property: |
| |
| ```python |
| constraint_value( |
| name = "x86", |
| constraint_setting = ":cpu" |
| ) |
| ``` |
| |
| These can be referenced as labels when defining platforms or customizing build rules |
| on them. If the above examples are defined in `cpus/BUILD`, you can reference |
| the `x86` constraint as `//cpus:x86`. |
| |
| If visibility allows, you can extend an existing `constraint_setting` by |
| defining your own value for it. |
| |
| ## Defining platforms {:#platforms} |
| |
| The [`platform`](/reference/be/platforms-and-toolchains#platform) build rule |
| defines a platform as a collection of `constraint_value`s: |
| |
| ```python |
| platform( |
| name = "linux_x86", |
| constraint_values = [ |
| "//oses:linux", |
| "//cpus:x86", |
| ], |
| ) |
| ``` |
| |
| This models a machine that must have both the `//oses:linux` and `//cpus:x86` |
| constraints. |
| |
| Platforms may only have one `constraint_value` for a given `constraint_setting`. |
| This means, for example, a platform can't have two CPUs unless you create another |
| `constraint_setting` type to model the second value. |
| |
| ## Skipping incompatible targets {:#skipping-incompatible-targets} |
| |
| When building for a specific target platform it is often desirable to skip |
| targets that will never work on that platform. For example, your Windows device |
| driver is likely going to generate lots of compiler errors when building on a |
| Linux machine with `//...`. Use the |
| [`target_compatible_with`](/reference/be/common-definitions#common.target_compatible_with) |
| attribute to tell Bazel what target platform constraints your code has. |
| |
| The simplest use of this attribute restricts a target to a single platform. |
| The target won't be built for any platform that doesn't satisfy all of the |
| constraints. The following example restricts `win_driver_lib.cc` to 64-bit |
| Windows. |
| |
| ```python |
| cc_library( |
| name = "win_driver_lib", |
| srcs = ["win_driver_lib.cc"], |
| target_compatible_with = [ |
| "@platforms//cpu:x86_64", |
| "@platforms//os:windows", |
| ], |
| ) |
| ``` |
| |
| `:win_driver_lib` is *only* compatible for building with 64-bit Windows and |
| incompatible with all else. Incompatibility is transitive. Any targets |
| that transitively depend on an incompatible target are themselves considered |
| incompatible. |
| |
| ### When are targets skipped? {:#when-targets-skipped} |
| |
| Targets are skipped when they are considered incompatible and included in the |
| build as part of a target pattern expansion. For example, the following two |
| invocations skip any incompatible targets found in a target pattern expansion. |
| |
| ```console |
| $ bazel build --platforms=//:myplatform //... |
| ``` |
| |
| ```console |
| $ bazel build --platforms=//:myplatform //:all |
| ``` |
| |
| Incompatible tests in a [`test_suite`](/reference/be/general#test_suite) are |
| similarly skipped if the `test_suite` is specified on the command line with |
| [`--expand_test_suites`](/reference/command-line-reference#flag--expand_test_suites). |
| In other words, `test_suite` targets on the command line behave like `:all` and |
| `...`. Using `--noexpand_test_suites` prevents expansion and causes |
| `test_suite` targets with incompatible tests to also be incompatible. |
| |
| Explicitly specifying an incompatible target on the command line results in an |
| error message and a failed build. |
| |
| ```console |
| $ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform |
| ... |
| ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested. |
| ... |
| FAILED: Build did NOT complete successfully |
| ``` |
| |
| Incompatible explicit targets are silently skipped if |
| `--skip_incompatible_explicit_targets` is enabled. |
| |
| ### More expressive constraints {:#expressive-constraints} |
| |
| For more flexibility in expressing constraints, use the |
| `@platforms//:incompatible` |
| [`constraint_value`](/reference/be/platforms-and-toolchains#constraint_value) |
| that no platform satisfies. |
| |
| Use [`select()`](/reference/be/functions#select) in combination with |
| `@platforms//:incompatible` to express more complicated restrictions. For |
| example, use it to implement basic OR logic. The following marks a library |
| compatible with macOS and Linux, but no other platforms. |
| |
| Note: An empty constraints list is equivalent to "compatible with everything". |
| |
| ```python |
| cc_library( |
| name = "unixish_lib", |
| srcs = ["unixish_lib.cc"], |
| target_compatible_with = select({ |
| "@platforms//os:osx": [], |
| "@platforms//os:linux": [], |
| "//conditions:default": ["@platforms//:incompatible"], |
| }), |
| ) |
| ``` |
| |
| The above can be interpreted as follows: |
| |
| 1. When targeting macOS, the target has no constraints. |
| 2. When targeting Linux, the target has no constraints. |
| 3. Otherwise, the target has the `@platforms//:incompatible` constraint. Because |
| `@platforms//:incompatible` is not part of any platform, the target is |
| deemed incompatible. |
| |
| To make your constraints more readable, use |
| [skylib](https://github.com/bazelbuild/bazel-skylib){: .external}'s |
| [`selects.with_or()`](https://github.com/bazelbuild/bazel-skylib/blob/main/docs/selects_doc.md#selectswith_or){: .external}. |
| |
| You can express inverse compatibility in a similar way. The following example |
| describes a library that is compatible with everything _except_ for ARM. |
| |
| ```python |
| cc_library( |
| name = "non_arm_lib", |
| srcs = ["non_arm_lib.cc"], |
| target_compatible_with = select({ |
| "@platforms//cpu:arm": ["@platforms//:incompatible"], |
| "//conditions:default": [], |
| }), |
| ) |
| ``` |
| |
| ### Detecting incompatible targets using `bazel cquery` {:#cquery-incompatible-target-detection} |
| |
| You can use the |
| [`IncompatiblePlatformProvider`](/rules/lib/providers/IncompatiblePlatformProvider) |
| in `bazel cquery`'s [Starlark output |
| format](/query/cquery#output-format-definition) to distinguish |
| incompatible targets from compatible ones. |
| |
| This can be used to filter out incompatible targets. The example below will |
| only print the labels for targets that are compatible. Incompatible targets are |
| not printed. |
| |
| ```console |
| $ cat example.cquery |
| |
| def format(target): |
| if "IncompatiblePlatformProvider" not in providers(target): |
| return target.label |
| return "" |
| |
| |
| $ bazel cquery //... --output=starlark --starlark:file=example.cquery |
| ``` |
| |
| ### Known Issues |
| |
| Incompatible targets [ignore visibility |
| restrictions](https://github.com/bazelbuild/bazel/issues/16044). |