Migrating to Platforms

Report an issue View source

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

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_settings 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:

  1. Your project's rules choose the right toolchains for //:myplatform.
  2. Your project's dependencies choose the right toolchains for //:myplatform.
  3. //:myplatform references common declarations of CPU, OS, and other generic, language-independent properties
  4. All relevant select()s properly match //:myplatform.
  5. //: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:

  1. //:myplatform must exist. It's generally the project owner's responsibility to define platforms because different projects target different machines. See Default platforms.

  2. 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.

  3. select()s and configuration transitions must resolve properly. See select() and Transitions.

  4. 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 @local_config_platform//:host so there's no need to explicitly define it. It maps the local machine's OS and CPU with constraint_values 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_values, 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.

selects 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:

  1. Have rule logic resolve toolchains with the toolchain API. See toolchain API (ctx.toolchains).

  2. 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.

  3. Define the relevant properties that make up platform components. See Common platform properties

  4. Define standard toolchains and make them accessible to users through your rule's registration instructions (details)

  5. 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_values 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.

See also