> ## Documentation Index
> Fetch the complete documentation index at: https://aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-compilation to target platforms

> Cross-compile C/C++ code with Bazel by defining platforms with OS, CPU, and ABI constraints, enabling builds for Linux aarch64 and other targets.

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:

```python theme={null}
platform(
    name = "linux_aarch64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:aarch64"
    ],
)
```

Platforms can also include map of properties for executing actions on them, typically used with Remote Build Execution:

```python theme={null}
platform(
    name = "x86_64_linux_remote",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    exec_properties = {
        "OSFamily": "Linux",
        "container-image": "docker://ghcr.io/catthehacker/ubuntu:act-22.04@sha256:5f9c35c25db1d51a8ddaae5c0ba8d3c163c5e9a4a6cc97acd409ac7eae239448",
    },
)
```

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:

* [https://github.com/scasagrande/toolchains\_llvm\_sysroot](https://github.com/scasagrande/toolchains_llvm_sysroot)
* [https://github.com/keith/bazel-cc-sysroot-generator](https://github.com/keith/bazel-cc-sysroot-generator)
* [https://github.com/malt3/sysroots](https://github.com/malt3/sysroots)
* [https://github.com/f0rmiga/gcc-toolchain](https://github.com/f0rmiga/gcc-toolchain)
* [https://github.com/lukasoyen/bazel\_linux\_packages](https://github.com/lukasoyen/bazel_linux_packages)

# 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`

```python theme={null}

# fetch a sysroot built for GNU/Linux 4.18.0
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "sysroot_linux_aarch64_2_28",
    url = "https://github.com/scasagrande/toolchains_llvm_sysroot/releases/download/linux-sysroot-4_18-2_28-10_3_0/linux-sysroot-aarch64.tar.zst",
    integrity = "sha256-hGKzwbu31ua17Ejjtk1ZZxFVsKUioo47yr72e87T4Ag=",
    # Don’t forget to create an empty BUILD file in vendor directory
    build_file = "//vendor:sysroot.BUILD"
)

# And instruct llvm toolchain to the sysroot we fetched above for any linux-aarch64
# builds.
llvm.sysroot(
    name = "llvm_toolchain",
    label = "@sysroot_linux_aarch64_2_28//:sysroot",
    targets = ["linux-aarch64"],
)
```

```python theme={null}
filegroup(
    name = "sysroot",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"]
)
```

Now let’s define the target platform and a cc\_binary in our `BUILD` file

```python theme={null}
cc_binary(
    name = "main",
    srcs = ["main.cc"],
)
```

./main.cc

```c theme={null}
#include <stdio.h>
#include <gnu/libc-version.h>

int main() {
	printf("hello\n");
	printf("GNU libc version is: %s\n", gnu_get_libc_version());
	return 0;
}
```

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

```python theme={null}
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
oci.pull(
    name = "cc_debian12",
    digest = "sha256:c53c9416a1acdbfd6e09abba720442444a3d1a6338b8db850e5e198b59af5570",
    image = "gcr.io/distroless/cc-debian12",
    platforms = [
        "linux/arm64/v8",
    ],
)
use_repo(oci, "cc_debian12")
```

Append to `BUILD.bazel`

```python theme={null}

load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
load("@tar.bzl", "tar")

tar(
    name = "binary_layer",
    mtree = ["./usr/bin/say_hi type=file contents=$(location :main)"],
    srcs = [":main"]
)

oci_image(
    name = "image",
    base = "@cc_debian12",
    tars = [":binary_layer"],
    entrypoint = ["/usr/bin/say_hi"]
)

oci_load(
    name = "load_into_docker",
    image = ":image",
    repo_tags = ["app:latest"]
)
```

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.

```c theme={null}
hello
GNU libc version is: 2.36
```

## 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](https://bazel.build/extending/config#user-defined-transitions).

<Note>
  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.
</Note>

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.

```python theme={null}
load("@aspect_bazel_lib//lib:transitions.bzl", "platform_transition_binary")

platform_transition_binary(
  name = "main_aarch64",
  binary = ":main",
  target_platform = "//tools/platforms:linux_aarch64"
)
```

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.
