xref: /aosp_15_r20/external/bazelbuild-rules_rust/examples/bzlmod/cross_compile/README.md (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1# Cross Compilation
2
3For cross compilation, you have to specify a custom platform to let Bazel know that you are compiling for a different platform than the default host platform.
4
5The example code is setup to cross compile from the following hosts to the the following targets:
6
7* {linux, x86_64} -> {linux, aarch64}
8* {darwin, x86_64} -> {linux, x86_64}
9* {darwin, x86_64} -> {linux, aarch64}
10* {darwin, aarch64 (Apple Silicon)} -> {linux, x86_64}
11* {darwin, aarch64 (Apple Silicon)} -> {linux, aarch64}
12
13You cross-compile by calling the target.
14
15`bazel build //:hello_world_x86_64`
16
17or
18
19`bazel build //:hello_world_aarch64`
20
21
22You can also build all targets at once:
23
24
25`bazel build //...`
26
27And you can run all test with:
28
29`bazel test //...`
30
31
32## Setup
33
34The setup requires three steps, first declare dependencies and toolchains in your MODULE.bazel, second configure LLVM and Rust for cross compilation, and third the configuration of the cross compilation platforms so you can use it binary targets.
35
36### Dependencies Configuration
37
38You add the required rules for cross compilation to your MODULE.bazel as shown below.
39
40```Starlark
41# Rules for cross compilation
42# https://github.com/bazelbuild/platforms/releases
43bazel_dep(name = "platforms", version = "0.0.10")
44# https://github.com/bazel-contrib/toolchains_llvm
45bazel_dep(name = "toolchains_llvm", version = "1.0.0")
46```
47
48## LLVM Configuration
49
50Next, you have to configure the LLVM toolchain because rules_rust still needs a cpp toolchain for cross compilation and
51you have to add the specific platform triplets to the Rust toolchain. Suppose you want to compile a Rust binary that
52supports linux on both, X86 and ARM. In that case, you have to setup three LLVM toolchains:
53
541) LLVM for the host
552) LLVM for X86
563) LLVM for ARM (aarch64)
57
58For the host LLVM, you just specify a LLVM version and then register the toolchain as usual. The target LLVM toolchains,
59however, have dependencies on system libraries for the target platform. Therefore, it is required to download a so-
60called sysroot that contains a root file system with all those system libraries for the specific target platform.
61To do so, please add the following to your MODULE.bazel
62
63```Starlark
64# https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/repo/http.bzl
65http_archive = use_repo_rule("@bazel_tools//:http.bzl", "http_archive")
66
67# Both, cross compilation and MUSL still need a C/C++ toolchain with sysroot.
68_BUILD_FILE_CONTENT = """
69filegroup(
70  name = "{name}",
71  srcs = glob(["*/**"]),
72  visibility = ["//visibility:public"],
73)
74"""
75
76# Download sysroot
77# https://commondatastorage.googleapis.com/chrome-linux-sysroot/
78http_archive(
79    name = "org_chromium_sysroot_linux_x64",
80    build_file_content = _BUILD_FILE_CONTENT.format(name = "sysroot"),
81    sha256 = "f6b758d880a6df264e2581788741623320d548508f07ffc2ae6a29d0c13d647d",
82    urls = ["https://commondatastorage.googleapis.com/chrome-linux-sysroot/toolchain/2e7ada854015a4cc60fc812112d261af44213ed0/debian_bullseye_amd64_sysroot.tar.xz"],
83)
84
85http_archive(
86    name = "org_chromium_sysroot_linux_aarch64",
87    build_file_content = _BUILD_FILE_CONTENT.format(name = "sysroot"),
88    sha256 = "902d1a40a5fd8c3764a36c8d377af5945a92e3d264c6252855bda4d7ef81d3df",
89    urls = ["https://commondatastorage.googleapis.com/chrome-linux-sysroot/toolchain/41a6c8dec4c4304d6509e30cbaf9218dffb4438e/debian_bullseye_arm64_sysroot.tar.xz"],
90)
91```
92
93Here, we declare to new http downloads that retrieve the sysroot for linux_x64 (Intel/AMD) and linux_aarch64 (ARM/Apple Silicon). Note, these are only
94sysroots, that means you have to configure LLVM next to use these files. As mentioned earlier, three LLVM toolchains
95needs to be configured and to do that, please add the following to your MODULE.bazel
96
97```Starlark
98LLVM_VERSIONS = {
99    "": "16.0.0",
100}
101
102# Host LLVM toolchain.
103llvm.toolchain(
104    name = "llvm_toolchain",
105    llvm_versions = LLVM_VERSIONS,
106)
107use_repo(llvm, "llvm_toolchain", "llvm_toolchain_llvm")
108
109# X86 LLVM Toolchain with sysroot.
110# https://github.com/bazel-contrib/toolchains_llvm/blob/master/tests/WORKSPACE.bzlmod
111llvm.toolchain(
112    name = "llvm_toolchain_x86_with_sysroot",
113    llvm_versions = LLVM_VERSIONS,
114)
115llvm.sysroot(
116    name = "llvm_toolchain_x86_with_sysroot",
117    label = "@org_chromium_sysroot_linux_x64//:sysroot",
118    targets = ["linux-x86_64"],
119)
120use_repo(llvm, "llvm_toolchain_x86_with_sysroot")
121
122#
123# ARM (aarch64) LLVM Toolchain with sysroot.
124# https://github.com/bazelbuild/rules_rust/blob/main/examples/bzlmod/cross_compile/WORKSPACE.bzlmod
125llvm.toolchain(
126    name = "llvm_toolchain_aarch64_with_sysroot",
127    llvm_versions = LLVM_VERSIONS,
128)
129llvm.sysroot(
130    name = "llvm_toolchain_aarch64_with_sysroot",
131    label = "@org_chromium_sysroot_linux_aarch64//:sysroot",
132    targets = ["linux-aarch64"],
133)
134use_repo(llvm, "llvm_toolchain_aarch64_with_sysroot")
135
136# Register all LLVM toolchains
137register_toolchains("@llvm_toolchain//:all")
138```
139
140For simplicity, all toolchains are pinned to version LLVM 16 because it is one of the few releases that supports the
141host (apple-darwin / Ubuntu), and the two targets. For a
142complete [list off all LLVM releases and supported platforms, see this list.](https://github.com/bazel-contrib/toolchains_llvm/blob/master/toolchain/internal/llvm_distributions.bzl)
143It is possible to pin different targets to different LLVM
144versions; [see the documentation for details](https://github.com/bazel-contrib/toolchains_llvm/tree/master?tab=readme-ov-file#per-host-architecture-llvm-version).
145
146If you face difficulties with building LLVM on older linux distros or your CI,
147please take a look at the [LLVM Troubleshooting guide](LLVM_Troubleshooting.md) for known issues.
148
149
150**Rust Toolchain Configuration**
151
152The Rust toolchain only need to know the additional platform triplets to download the matching toolchains. To do so, add
153or or modify your MODULE.bazel with the following entry:
154
155```Starlark
156# Rust toolchain
157RUST_EDITION = "2021"
158RUST_VERSION = "1.79.0"
159
160rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
161rust.toolchain(
162    edition = RUST_EDITION,
163    versions = [RUST_VERSION],
164    extra_target_triples = [
165        "aarch64-unknown-linux-gnu",
166        "x86_64-unknown-linux-gnu",
167    ],
168)
169use_repo(rust, "rust_toolchains")
170register_toolchains("@rust_toolchains//:all")
171```
172
173You find the exact platform triplets in
174the [Rust platform support documentation](https://doc.rust-lang.org/nightly/rustc/platform-support.html).
175Next, you have to configure the target platform.
176
177**Platform Configuration**
178
179Once the dependencies are loaded, create an empty BUILD file to define the cross compilation toolchain targets.
180As mentioned earlier, it is best practice to put all custom rules, toolchains, and platform into one folder.
181Suppose you have the empty BUILD file in the following path:
182
183`build/platforms/BUILD.bazel`
184
185Then you add the following content to the BUILD file:
186
187```Starlark
188package(default_visibility = ["//visibility:public"])
189
190platform(
191    name = "linux-aarch64",
192    constraint_values = [
193        "@platforms//os:linux",
194        "@platforms//cpu:aarch64",
195    ],
196)
197
198platform(
199    name = "linux-x86_64",
200    constraint_values = [
201        "@platforms//os:linux",
202        "@platforms//cpu:x86_64",
203    ],
204)
205```
206
207The default visibility at the top of the file means that all targets in this BUILD file will be public by default, which
208is sensible because cross-compilation targets are usually used across the entire project.
209
210It is important to recognize that the platform rules use the constraint values to map those constraints to the target
211triplets of the Rust toolchain. If you somehow see errors that says some crate couldn't be found with triple xyz, then
212one of two things happened.
213
214Either you forgot to add a triple to the Rust toolchain. Unfortunately, the error message
215doesn't always tell you the correct triple that is missing. However, in that case you have to double check if for each
216specified platform a corresponding Rust extra_target_triples has been added. If one is missing, add it and the error
217goes away.
218
219A second source of error is if the platform declaration contains a typo, for example,
220cpu:arch64 instead of cpu:aarch64. You have to be meticulous in the platform declaration to make everything work
221smoothly.
222
223With the platform configuration out of the way, you are free to configure your binary targets for the specified
224platforms.
225
226## Usage
227
228Suppose you have a simple hello world that is defined in a single main.rs file. Conventionally, you declare a minimum
229binary target as shown below.
230
231```Starlark
232load("@rules_rust//rust:defs.bzl", "rust_binary")
233
234rust_binary(
235    name = "hello_world_host",
236    srcs = ["src/main.rs"],
237    deps = [],
238)
239```
240
241Bazel compiles this target to the same platform as the host. To cross-compile the same source file to a different
242platform, you simply add one of the platforms previously declared, as shown below.
243
244```Starlark
245load("@rules_rust//rust:defs.bzl", "rust_binary")
246
247rust_binary(
248    name = "hello_world_x86_64",
249    srcs = ["src/main.rs"],
250    platform = "//build/platforms:linux-x86_64",
251    deps = [],
252)
253
254rust_binary(
255    name = "hello_world_aarch64",
256    srcs = ["src/main.rs"],
257    platform = "//build/platforms:linux-aarch64",
258    deps = [],
259)
260```
261
262You then cross-compile by calling the target.
263
264`bazel build //:hello_world_x86_64`
265
266or
267
268`bazel build //:hello_world_aarch64`
269
270You may have to make the target public when see an access error.
271
272However, when you build for multiple targets, it is sensible to group all of them in a filegroup.
273
274```Starlark
275filegroup(
276    name = "all",
277    srcs = [
278        ":hello_world_host",
279        ":hello_world_x86_64",
280        ":hello_world_aarch64",
281    ],
282    visibility = ["//visibility:public"],
283)
284```
285
286Then you build for all platforms by calling the filegroup target:
287
288`bazel build //:all`
289