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 build actions should compile with.
Developers can also use constraints to select
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.
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:
$ 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 on these properties with the
@platforms/os and
@platforms/cpu
constraints.
Generally useful constraints and 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.
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 are modeled with the constraint_setting and
constraint_value build rules.
constraint_setting declares a type of property. For example:
constraint_setting(name = "cpu")
constraint_value declares a possible value for that property:
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
The platform build rule
defines a platform as a collection of constraint_values:
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
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
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.
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?
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.
$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all
Incompatible tests in a test_suite are
similarly skipped if the test_suite is specified on the command line with
--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.
$ 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
For more flexibility in expressing constraints, use the
@platforms//:incompatible
constraint_value
that no platform satisfies.
Use 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.
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:
- When targeting macOS, the target has no constraints.
- When targeting Linux, the target has no constraints.
- Otherwise, the target has the
@platforms//:incompatibleconstraint. Because@platforms//:incompatibleis not part of any platform, the target is deemed incompatible.
To make your constraints more readable, use
skylib's
selects.with_or().
You can express inverse compatibility in a similar way. The following example describes a library that is compatible with everything except for ARM.
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
You can use the
IncompatiblePlatformProvider
in bazel cquery's Starlark output
format 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.
$ 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.