Skip to main content
A “platform” is user-defined, and describes the operating system, CPU architecture, and the runtime ABI to dynamic-link against. Bazel includes some constraints in the @platforms repo, for example:
Platforms can also include map of properties for executing actions on them, typically used with Remote Build Execution:
There are several platforms involved in a build:
  1. Dev: machine where the code is cloned
  2. Host: machine where Bazel is running
  3. Exec: machine where tools like compilers run
  4. Target: machine where the built program will run
Cross-compiling produces binaries for a different target platform from the exec platform.

Requirements of cross compilation

  • Compiler toolchain that supports the particular combination of exec → target platform. eg llvm/clang.
  • A sysroot that matches the target platform

Why cross-compile?

  • You can compile code for any target platform regardless of where Bazel runs
  • Fully reproducible and repeatable, eg: any update the developer machine does not break the build
  • No glibc version skews that surface later during deployment

Sysroot

A sysroot contains all the linkable libraries and headers needed for compilation and linking. Typical contents include /usr/lib[64] and /usr/include . Here is a list of sysroot generators we know of:

Example: Targeting GNU/Linux arm64

For the following example, we will not build our own sysroot, instead we will use an existing sysroot for Linux aarch64 CPU architecture. We’ll just use the first one in the list above, but don’t have a strong reason to prefer one over another. We can run the llvm/clang compiler to compile a simple C binary for a target platform. Add this to MODULE.bazel
Now let’s define the target platform and a cc_binary in our BUILD file
./main.cc
Now you can compile the binary above by running; bazel build :main --platforms=//tools/platforms:linux_aarch64 Now let’s put this binary into a Docker container with a newer glibc version than the sysroot, which ought to work since glibc releases are backwards-compatible. Append to MODULE.bazel:
Append to BUILD.bazel
Now run bazel run :load_into_docker --platforms=//tools/platforms:linux_aarch64 which will build your binary and put it into a container and load onto the docker daemon running locally. Once it loads, run docker run app:latest, you should see the following output.

Transitions

By default Bazel will compile your code for the platform that you are running on and in order to tell it to compile for a specific target platform, we have to use the --platforms flag. However this doesn’t work for more than one platform simultaneously, even though the flag signifies plurality. In addition to this limitation, it’s not always easy to remember what flags you are supposed to pass. Instead, we can use transitions.
Transitions define configuration changes between rules. For example, a request like “compile my dependency for a different CPU than its parent” is handled by a transition.
Let’s fix the BUILD file so that we don’t need to specify —platforms flag anymore, to do that we’ll use platform_transition_binary to transition our binary to be built for a specific platform.
Now we can build the binary using the following command: bazel build :main_aarch64

Targeting macOS

Cross compiling to macOS is almost identical to targeting GNU/Linux with a few considerations. The headers and libraries for targeting macOS are distributed by Apple and requires installation of Xcode, including accepting a Terms of Service. For instance Aspect builds its own macOS.sdk distribution for hermetic macOS builds.

Targeting Windows

Independent of Bazel, cross-compiling to Windows is not easy so we won’t be teaching it here.