blob: ca3c594b3adb65668e6d776fc5b3457d64a337aa [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
Tim Windelschmidt59b49c92025-07-09 06:54:49 +020041@@ -0,0 +1,51 @@
Jan Schär548cbe62025-07-03 16:26:11 +000042+#!/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+
Tim Windelschmidt59b49c92025-07-09 06:54:49 +020051+dirname() {
52+ # Usage: dirname "path"
53+
54+ # If '$1' is empty set 'dir' to '.', else '$1'.
55+ dir=${1:-.}
56+
57+ # Strip all trailing forward-slashes '/' from
58+ # the end of the string.
59+ #
60+ # "${dir##*[!/]}": Remove all non-forward-slashes
61+ # from the start of the string, leaving us with only
62+ # the trailing slashes.
63+ # "${dir%%"${}"}": Remove the result of the above
64+ # substitution (a string of forward slashes) from the
65+ # end of the original string.
66+ dir=${dir%%"${dir##*[!/]}"}
67+
68+ # If the variable *does not* contain any forward slashes
69+ # set its value to '.'.
70+ [ "${dir##*/*}" ] && dir=.
71+
72+ # Remove everything *after* the last forward-slash '/'.
73+ dir=${dir%/*}
74+
75+ # Again, strip all trailing forward-slashes '/' from
76+ # the end of the string (see above).
77+ dir=${dir%%"${dir##*[!/]}"}
78+
79+ # Print the resulting string and if it is empty,
80+ # print '/'.
81+ printf '%s\n' "${dir:-/}"
82+}
83+
Jan Schär548cbe62025-07-03 16:26:11 +000084+selfDir="$(dirname "$0")"
85+rootPath="$(dirname "$selfDir")"
86+
87+exec "${rootPath}/sysroot/usr/lib/libc.so" \
88+ "--library-path=${rootPath}/sysroot/usr/lib" \
89+ "--argv0" "$0" \
90+ "--" \
91+ "${0}_real" \
92+ "$@"
93diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
94index 27c08e78..0761c34c 100644
95--- a/rust/private/repository_utils.bzl
96+++ b/rust/private/repository_utils.bzl
97@@ -41,12 +41,16 @@ filegroup(
98 name = "rustc_lib",
99 srcs = glob(
100 [
101+ "bin/*_real",
102 "bin/*{dylib_ext}",
103 "lib/*{dylib_ext}*",
104 "lib/rustlib/{target_triple}/codegen-backends/*{dylib_ext}",
105 "lib/rustlib/{target_triple}/bin/rust-lld{binary_ext}",
106 "lib/rustlib/{target_triple}/lib/*{dylib_ext}*",
107 "lib/rustlib/{target_triple}/lib/*.rmeta",
108+ "sysroot/usr/lib/libc.so",
109+ "sysroot/usr/lib/libgcc_s.so*",
110+ "sysroot/usr/lib/libunwind.so*",
111 ],
112 allow_empty = True,
113 ),
114diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
115index d78c2890..fbc00591 100644
116--- a/rust/private/rustc.bzl
117+++ b/rust/private/rustc.bzl
118@@ -1012,6 +1012,12 @@ def construct_arguments(
119 if linker_script:
120 rustc_flags.add(linker_script, format = "--codegen=link-arg=-T%s")
121
122+ if is_exec_configuration(ctx):
123+ # This is needed when building proc macros, for lld to find libgcc_s.so
124+ # and libc.so. This must be before the Rust standard library paths so
125+ # lld uses libc.so instead of the libc.a shipped by Rust.
126+ rustc_flags.add("-L", "native={}/sysroot/usr/lib".format(tool_path.rsplit("/", 2)[0]))
127+
128 # Tell Rustc where to find the standard library (or libcore)
129 rustc_flags.add_all(toolchain.rust_std_paths, before_each = "-L", format_each = "%s")
130 rustc_flags.add_all(rust_flags, map_each = map_flag)
131diff --git a/rust/repositories.bzl b/rust/repositories.bzl
132index a5df3842..323a26ae 100644
133--- a/rust/repositories.bzl
134+++ b/rust/repositories.bzl
135@@ -42,13 +42,13 @@ load_arbitrary_tool = _load_arbitrary_tool
136 DEFAULT_TOOLCHAIN_TRIPLES = {
137 "aarch64-apple-darwin": "rust_macos_aarch64",
138 "aarch64-pc-windows-msvc": "rust_windows_aarch64",
139- "aarch64-unknown-linux-gnu": "rust_linux_aarch64",
140+ "aarch64-unknown-linux-musl": "rust_linux_aarch64",
141 "powerpc64le-unknown-linux-gnu": "rust_linux_powerpc64le",
142 "s390x-unknown-linux-gnu": "rust_linux_s390x",
143 "x86_64-apple-darwin": "rust_macos_x86_64",
144 "x86_64-pc-windows-msvc": "rust_windows_x86_64",
145 "x86_64-unknown-freebsd": "rust_freebsd_x86_64",
146- "x86_64-unknown-linux-gnu": "rust_linux_x86_64",
147+ "x86_64-unknown-linux-musl": "rust_linux_x86_64",
148 }
149
150 _COMPACT_WINDOWS_NAMES = True
151@@ -349,6 +349,33 @@ def rust_repositories(**kwargs):
152
153 rust_register_toolchains(**kwargs)
154
155+def load_sysroot(ctx, target_triple):
156+ # Download the sysroot. We only need libc.so and libunwind.so from this;
157+ # they are runtime dependencies of rustc, cargo, etc.
158+ ARTIFACTS_VERSION = "20250603"
159+ if target_triple.arch == "x86_64":
160+ ctx.download_and_extract(
161+ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-x86_64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
162+ sha256 = "ba0fce4438de6d4dbc1678d2e672f377d30327d24e10b0de09f090032d34c1ba",
163+ output = "sysroot",
164+ )
165+ elif target_triple.arch == "aarch64":
166+ ctx.download_and_extract(
167+ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-aarch64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
168+ sha256 = "0cee4d5c1d24a9e9e2d1d12fd5e05ea1afcf69ea0b5d43979290988143ce1a0f",
169+ output = "sysroot",
170+ )
171+ else:
172+ fail("missing sysroot for architecture {}".format(target_triple.arch))
173+ # Rust uses either libgcc_s.so or libunwind.so for unwinding. Both have the
174+ # same interface for unwinding, so we can make libgcc_s.so a symlink to
175+ # libunwind.so and it will work. This could potentially break in unknown
176+ # ways, but it didn't break so far. Unwinding was tested by inserting a
177+ # panic in a proc macro, which worked as expected. This avoids us needing to
178+ # either build libgcc_s.so or build Rust configured to use libunwind.so.
179+ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so.1")
180+ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so")
181+
182 _RUST_TOOLCHAIN_REPOSITORY_ATTRS = {
183 "allocator_library": attr.string(
184 doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
185@@ -539,6 +566,13 @@ def _rust_toolchain_tools_repository_impl(ctx):
186 repro[key] = getattr(ctx.attr, key)
187 repro["sha256s"] = sha256s
188
189+ load_sysroot(ctx = ctx, target_triple = exec_triple)
190+ loader_wrapper = ctx.read(Label("//rust/private:loader_wrapper.sh"))
191+ ctx.rename("bin/cargo", "bin/cargo_real")
192+ ctx.file("bin/cargo", loader_wrapper)
193+ ctx.rename("bin/rustc", "bin/rustc_real")
194+ ctx.file("bin/rustc", loader_wrapper)
195+
196 return repro
197
198 rust_toolchain_tools_repository = repository_rule(
199@@ -928,6 +962,11 @@ def _rustfmt_toolchain_tools_repository_impl(repository_ctx):
200 repro[key] = getattr(repository_ctx.attr, key)
201 repro["sha256s"] = sha256s
202
203+ load_sysroot(ctx = repository_ctx, target_triple = exec_triple)
204+ loader_wrapper = repository_ctx.read(Label("//rust/private:loader_wrapper.sh"))
205+ repository_ctx.rename("bin/rustfmt", "bin/rustfmt_real")
206+ repository_ctx.file("bin/rustfmt", loader_wrapper)
207+
208 return repro
209
210 rustfmt_toolchain_tools_repository = repository_rule(