blob: 81fce0486e39df8b2339158261c4d7340880dc77 [file] [log] [blame]
Jan Schär548cbe62025-07-03 16:26:11 +00001commit e48fd8490d497fdfa6d5a7f8a3af63cc8c6db538
2Author: Jan Schär <jan@monogon.tech>
3Date: Thu Jul 3 16:03:25 2025 +0000
4
5 Use musl toolchain and add loader_wrapper
6
7 This patches rules_rust to use the Rust toolchain built for musl instead
8 of glibc, and adds a wrapper to locate the dynamic loader. What we
9 achieve with this is that we no longer need a specific loader installed
10 at an absolute path, which makes the build less dependent on the host
11 OS.
12
13 The tools are linked dynamically and need libc.so and libgcc_s.so.1. At
14 least rustc cannot be linked statically, because proc macros are loaded
15 with dlopen. When building proc macros, we need to add library paths
16 containing the two .so files such that lld can find them.
17
18 Usually, musl would be installed at /lib/ld-musl-x86_64.so.1, but we
19 can't do that. Instead, the binaries are renamed to {name}_real and a
20 wrapper script is placed there instead. The wrapper runs musl as a
21 dynamic loader, passing it the path to the real binary.
22
23diff --git a/rust/platform/triple.bzl b/rust/platform/triple.bzl
24index 87cd6242..766bc08e 100644
25--- a/rust/platform/triple.bzl
26+++ b/rust/platform/triple.bzl
27@@ -138,7 +138,7 @@ def get_host_triple(repository_ctx, abi = {}):
28 prefix = "{}-unknown-linux".format(arch)
29 return triple("{}-{}".format(
30 prefix,
31- abi.get(prefix, "gnu"),
32+ abi.get(prefix, "musl"),
33 ))
34
35 if "mac" in repository_ctx.os.name:
36diff --git a/rust/private/loader_wrapper.sh b/rust/private/loader_wrapper.sh
37new file mode 100755
38index 00000000..29805d8d
39--- /dev/null
40+++ b/rust/private/loader_wrapper.sh
41@@ -0,0 +1,18 @@
42+#!/bin/sh
43+
44+# Running rustc and other tools directly doesn't work, because that would
45+# require musl to be installed at /lib/ld-musl-x86_64.so.1, but we can't install
46+# things at absolute paths. Instead, this script calls musl as a dynamic loader,
47+# giving it the path to the real binary which has been renamed to {name}_real.
48+
49+set -eu
50+
51+selfDir="$(dirname "$0")"
52+rootPath="$(dirname "$selfDir")"
53+
54+exec "${rootPath}/sysroot/usr/lib/libc.so" \
55+ "--library-path=${rootPath}/sysroot/usr/lib" \
56+ "--argv0" "$0" \
57+ "--" \
58+ "${0}_real" \
59+ "$@"
60diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
61index 27c08e78..0761c34c 100644
62--- a/rust/private/repository_utils.bzl
63+++ b/rust/private/repository_utils.bzl
64@@ -41,12 +41,16 @@ filegroup(
65 name = "rustc_lib",
66 srcs = glob(
67 [
68+ "bin/*_real",
69 "bin/*{dylib_ext}",
70 "lib/*{dylib_ext}*",
71 "lib/rustlib/{target_triple}/codegen-backends/*{dylib_ext}",
72 "lib/rustlib/{target_triple}/bin/rust-lld{binary_ext}",
73 "lib/rustlib/{target_triple}/lib/*{dylib_ext}*",
74 "lib/rustlib/{target_triple}/lib/*.rmeta",
75+ "sysroot/usr/lib/libc.so",
76+ "sysroot/usr/lib/libgcc_s.so*",
77+ "sysroot/usr/lib/libunwind.so*",
78 ],
79 allow_empty = True,
80 ),
81diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
82index d78c2890..fbc00591 100644
83--- a/rust/private/rustc.bzl
84+++ b/rust/private/rustc.bzl
85@@ -1012,6 +1012,12 @@ def construct_arguments(
86 if linker_script:
87 rustc_flags.add(linker_script, format = "--codegen=link-arg=-T%s")
88
89+ if is_exec_configuration(ctx):
90+ # This is needed when building proc macros, for lld to find libgcc_s.so
91+ # and libc.so. This must be before the Rust standard library paths so
92+ # lld uses libc.so instead of the libc.a shipped by Rust.
93+ rustc_flags.add("-L", "native={}/sysroot/usr/lib".format(tool_path.rsplit("/", 2)[0]))
94+
95 # Tell Rustc where to find the standard library (or libcore)
96 rustc_flags.add_all(toolchain.rust_std_paths, before_each = "-L", format_each = "%s")
97 rustc_flags.add_all(rust_flags, map_each = map_flag)
98diff --git a/rust/repositories.bzl b/rust/repositories.bzl
99index a5df3842..323a26ae 100644
100--- a/rust/repositories.bzl
101+++ b/rust/repositories.bzl
102@@ -42,13 +42,13 @@ load_arbitrary_tool = _load_arbitrary_tool
103 DEFAULT_TOOLCHAIN_TRIPLES = {
104 "aarch64-apple-darwin": "rust_macos_aarch64",
105 "aarch64-pc-windows-msvc": "rust_windows_aarch64",
106- "aarch64-unknown-linux-gnu": "rust_linux_aarch64",
107+ "aarch64-unknown-linux-musl": "rust_linux_aarch64",
108 "powerpc64le-unknown-linux-gnu": "rust_linux_powerpc64le",
109 "s390x-unknown-linux-gnu": "rust_linux_s390x",
110 "x86_64-apple-darwin": "rust_macos_x86_64",
111 "x86_64-pc-windows-msvc": "rust_windows_x86_64",
112 "x86_64-unknown-freebsd": "rust_freebsd_x86_64",
113- "x86_64-unknown-linux-gnu": "rust_linux_x86_64",
114+ "x86_64-unknown-linux-musl": "rust_linux_x86_64",
115 }
116
117 _COMPACT_WINDOWS_NAMES = True
118@@ -349,6 +349,33 @@ def rust_repositories(**kwargs):
119
120 rust_register_toolchains(**kwargs)
121
122+def load_sysroot(ctx, target_triple):
123+ # Download the sysroot. We only need libc.so and libunwind.so from this;
124+ # they are runtime dependencies of rustc, cargo, etc.
125+ ARTIFACTS_VERSION = "20250603"
126+ if target_triple.arch == "x86_64":
127+ ctx.download_and_extract(
128+ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-x86_64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
129+ sha256 = "ba0fce4438de6d4dbc1678d2e672f377d30327d24e10b0de09f090032d34c1ba",
130+ output = "sysroot",
131+ )
132+ elif target_triple.arch == "aarch64":
133+ ctx.download_and_extract(
134+ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-aarch64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
135+ sha256 = "0cee4d5c1d24a9e9e2d1d12fd5e05ea1afcf69ea0b5d43979290988143ce1a0f",
136+ output = "sysroot",
137+ )
138+ else:
139+ fail("missing sysroot for architecture {}".format(target_triple.arch))
140+ # Rust uses either libgcc_s.so or libunwind.so for unwinding. Both have the
141+ # same interface for unwinding, so we can make libgcc_s.so a symlink to
142+ # libunwind.so and it will work. This could potentially break in unknown
143+ # ways, but it didn't break so far. Unwinding was tested by inserting a
144+ # panic in a proc macro, which worked as expected. This avoids us needing to
145+ # either build libgcc_s.so or build Rust configured to use libunwind.so.
146+ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so.1")
147+ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so")
148+
149 _RUST_TOOLCHAIN_REPOSITORY_ATTRS = {
150 "allocator_library": attr.string(
151 doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
152@@ -539,6 +566,13 @@ def _rust_toolchain_tools_repository_impl(ctx):
153 repro[key] = getattr(ctx.attr, key)
154 repro["sha256s"] = sha256s
155
156+ load_sysroot(ctx = ctx, target_triple = exec_triple)
157+ loader_wrapper = ctx.read(Label("//rust/private:loader_wrapper.sh"))
158+ ctx.rename("bin/cargo", "bin/cargo_real")
159+ ctx.file("bin/cargo", loader_wrapper)
160+ ctx.rename("bin/rustc", "bin/rustc_real")
161+ ctx.file("bin/rustc", loader_wrapper)
162+
163 return repro
164
165 rust_toolchain_tools_repository = repository_rule(
166@@ -928,6 +962,11 @@ def _rustfmt_toolchain_tools_repository_impl(repository_ctx):
167 repro[key] = getattr(repository_ctx.attr, key)
168 repro["sha256s"] = sha256s
169
170+ load_sysroot(ctx = repository_ctx, target_triple = exec_triple)
171+ loader_wrapper = repository_ctx.read(Label("//rust/private:loader_wrapper.sh"))
172+ repository_ctx.rename("bin/rustfmt", "bin/rustfmt_real")
173+ repository_ctx.file("bin/rustfmt", loader_wrapper)
174+
175 return repro
176
177 rustfmt_toolchain_tools_repository = repository_rule(