Bazel has sophisticated support for modeling platforms and toolchains for multi-architecture and cross-compiled builds.
This page summarizes the state of this support.
See also:
Status
C++
C++ rules use platforms to select toolchains when
--incompatible_enable_cc_toolchain_resolution
is set.
This means you can configure a C++ project with:
bazel build //:my_cpp_project --platforms=//:myplatform
instead of the legacy:
bazel build //:my_cpp_project` --cpu=... --crosstool_top=... --compiler=...
This will be enabled by default in Bazel 7.0 (#7260).
To test your C++ project with platforms, see Migrating Your Project and Configuring C++ toolchains.
Java
Java rules use platforms to select toolchains.
This replaces legacy flags --java_toolchain
, --host_java_toolchain
,
--javabase
, and --host_javabase
.
See Java and Bazel for details.
Android
Android rules use platforms to select toolchains when
--incompatible_enable_android_toolchain_resolution
is set.
This means you can configure an Android project with:
bazel build //:my_android_project --android_platforms=//:my_android_platform
instead of with legacy flags like --android_crosstool_top
, --android_cpu
,
and --fat_apk_cpu
.
This will be enabled by default in Bazel 7.0 (#16285).
To test your Android project with platforms, see Migrating Your Project.
Apple
Apple rules do not support platforms and are not yet scheduled for support.
You can still use platform APIs with Apple builds (for example, when building with a mixture of Apple rules and pure C++) with platform mappings.
Other languages
- Go rules fully support platforms
- Rust rules fully support platforms.
If you own a language rule set, see Migrating your rule set for adding support.
Background
Platforms and toolchains were introduced to standardize how software projects target different architectures and cross-compile.
This was
inspired
by the observation that language maintainers were already doing this in ad
hoc, incompatible ways. For example, C++ rules used --cpu
and
--crosstool_top
to declare a target CPU and toolchain. Neither of these
correctly models a "platform". This produced awkward and incorrect builds.
Java, Android, and other languages evolved their own flags for similar purposes, none of which interoperated with each other. This made cross-language builds confusing and complicated.
Bazel is intended for large, multi-language, multi-platform projects. This demands more principled support for these concepts, including a clear standard API.
Need for migration
Upgrading to the new API requires two efforts: releasing the API and upgrading rule logic to use it.
The first is done but the second is ongoing. This consists of ensuring
language-specific platforms and toolchains are defined, language logic reads
toolchains through the new API instead of old flags like --crosstool_top
, and
config_setting
s select on the new API instead of old flags.
This work is straightforward but requires a distinct effort for each language, plus fair warning for project owners to test against upcoming changes.
This is why this is an ongoing migration.
Goal
This migration is complete when all projects build with the form:
bazel build //:myproject --platforms=//:myplatform
This implies:
- Your project's rules choose the right toolchains for
//:myplatform
. - Your project's dependencies choose the right toolchains for
//:myplatform
. //:myplatform
references common declarations ofCPU
,OS
, and other generic, language-independent properties- All relevant
select()
s properly match//:myplatform
. //:myplatform
is defined in a clear, accessible place: in your project's repo if the platform is unique to your project, or some common place all consuming projects can find it
Old flags like --cpu
, --crosstool_top
, and --fat_apk_cpu
will be
deprecated and removed as soon as it's safe to do so.
Ultimately, this will be the sole way to configure architectures.
Migrating your project
If you build with languages that support platforms, your build should already work with an invocation like:
bazel build //:myproject --platforms=//:myplatform
See Status and your language's documentation for precise details.
If a language requires a flag to enable platform support, you also need to set that flag. See Status for details.
For your project to build, you need to check the following:
//:myplatform
must exist. It's generally the project owner's responsibility to define platforms because different projects target different machines. See Default platforms.The toolchains you want to use must exist. If using stock toolchains, the language owners should include instructions for how to register them. If writing your own custom toolchains, you need to register them in your
MODULE.bazel
file or with--extra_toolchains
.select()
s and configuration transitions must resolve properly. See select() and Transitions.If your build mixes languages that do and don't support platforms, you may need platform mappings to help the legacy languages work with the new API. See Platform mappings for details.
If you still have problems, reach out for support.
Default platforms
Project owners should define explicit
platforms to describe the architectures
they want to build for. These are then triggered with --platforms
.
When --platforms
isn't set, Bazel defaults to a platform
representing the
local build machine. This is auto-generated at @platforms//host
(aliased as
@bazel_tools//tools:host_platform
)
so there's no need to explicitly define it. It maps the local machine's OS
and CPU
with constraint_value
s declared in
@platforms
.
select()
Projects can select()
on
constraint_value
targets but not complete
platforms. This is intentional so select()
supports as wide a variety of
machines as possible. A library with ARM
-specific sources should support all
ARM
-powered machines unless there's reason to be more specific.
To select on one or more constraint_value
s, use:
config_setting(
name = "is_arm",
constraint_values = [
"@platforms//cpu:arm",
],
)
This is equivalent to traditionally selecting on --cpu
:
config_setting(
name = "is_arm",
values = {
"cpu": "arm",
},
)
More details here.
select
s on --cpu
, --crosstool_top
, etc. don't understand --platforms
.
When migrating your project to platforms, you must either convert them to
constraint_values
or use platform mappings to support
both styles during migration.
Transitions
Starlark transitions change
flags down parts of your build graph. If your project uses a transition that
sets --cpu
, --crossstool_top
, or other legacy flags, rules that read
--platforms
won't see these changes.
When migrating your project to platforms, you must either convert changes like
return { "//command_line_option:cpu": "arm" }
to return {
"//command_line_option:platforms": "//:my_arm_platform" }
or use platform
mappings to support both styles during migration.
window.
Migrating your rule set
If you own a rule set and want to support platforms, you need to:
Have rule logic resolve toolchains with the toolchain API. See toolchain API (
ctx.toolchains
).Optional: define an
--incompatible_enable_platforms_for_my_language
flag so rule logic alternately resolves toolchains through the new API or old flags like--crosstool_top
during migration testing.Define the relevant properties that make up platform components. See Common platform properties
Define standard toolchains and make them accessible to users through your rule's registration instructions (details)
Ensure
select()
s and configuration transitions support platforms. This is the biggest challenge. It's particularly challenging for multi-language projects (which may fail if all languages can't read--platforms
).
If you need to mix with rules that don't support platforms, you may need platform mappings to bridge the gap.
Common platform properties
Common, cross-language platform properties like OS
and CPU
should be
declared in @platforms
.
This encourages sharing, standardization, and cross-language compatibility.
Properties unique to your rules should be declared in your rule's repo. This lets you maintain clear ownership over the specific concepts your rules are responsible for.
If your rules use custom-purpose OSes or CPUs, these should be declared in your
rule's repo vs.
@platforms
.
Platform mappings
Platform mappings is a temporary API that lets platform-aware logic mix with legacy logic in the same build. This is a blunt tool that's only intended to smooth incompatibilities with different migration timeframes.
A platform mapping is a map of either a platform()
to a
corresponding set of legacy flags or the reverse. For example:
platforms:
# Maps "--platforms=//platforms:ios" to "--cpu=ios_x86_64 --apple_platform_type=ios".
//platforms:ios
--cpu=ios_x86_64
--apple_platform_type=ios
flags:
# Maps "--cpu=ios_x86_64 --apple_platform_type=ios" to "--platforms=//platforms:ios".
--cpu=ios_x86_64
--apple_platform_type=ios
//platforms:ios
# Maps "--cpu=darwin_x86_64 --apple_platform_type=macos" to "//platform:macos".
--cpu=darwin_x86_64
--apple_platform_type=macos
//platforms:macos
Bazel uses this to guarantee all settings, both platform-based and legacy, are consistently applied throughout the build, including through transitions.
By default Bazel reads mappings from the platform_mappings
file in your
workspace root. You can also set
--platform_mappings=//:my_custom_mapping
.
See the platform mappings design for details.
API review
A platform
is a collection of
constraint_value
targets:
platform(
name = "myplatform",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:arm",
],
)
A constraint_value
is a machine
property. Values of the same "kind" are grouped under a common
constraint_setting
:
constraint_setting(name = "os")
constraint_value(
name = "linux",
constraint_setting = ":os",
)
constraint_value(
name = "mac",
constraint_setting = ":os",
)
A toolchain
is a Starlark rule. Its
attributes declare a language's tools (like compiler =
"//mytoolchain:custom_gcc"
). Its providers pass
this information to rules that need to build with these tools.
Toolchains declare the constraint_value
s of machines they can
target
(target_compatible_with = ["@platforms//os:linux"]
) and machines their tools can
run on
(exec_compatible_with = ["@platforms//os:mac"]
).
When building $ bazel build //:myproject --platforms=//:myplatform
, Bazel
automatically selects a toolchain that can run on the build machine and
build binaries for //:myplatform
. This is known as toolchain resolution.
The set of available toolchains can be registered in the MODULE.bazel
file
with register_toolchains
or at the
command line with --extra_toolchains
.
For more information see here.
Questions
For general support and questions about the migration timeline, contact bazel-discuss or the owners of the appropriate rules.
For discussions on the design and evolution of the platform/toolchain APIs, contact bazel-dev.