third_party/rules_rust: Use musl toolchain and add loader_wrapper
Change-Id: Ia873e6bf43ed8f6f3c56943dba0e893b7efb7f44
Reviewed-on: https://review.monogon.dev/c/monogon/+/4310
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/MODULE.bazel b/MODULE.bazel
index 2780c09..eaff967 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -42,6 +42,7 @@
patches = [
"//third_party/rules_rust:rust-prost-nostd.patch",
"//third_party/rules_rust:rust-reproducibility.patch",
+ "//third_party/rules_rust:musl.patch",
],
version = RULES_RUST_VERSION,
)
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index d6edd57..ca91e77 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -1431,7 +1431,7 @@
},
"@@rules_rust+//crate_universe:extension.bzl%crate": {
"general": {
- "bzlTransitiveDigest": "8V+EjcJ6yZ+quaNMoFSCDIH+XDhCkjgCsiium78SdCY=",
+ "bzlTransitiveDigest": "PuAP2g4Ru+pBugsbmCEnQMOa3RC4MAiUwq3IARY4syY=",
"usagesDigest": "Mw24CZ9hpdGUPbu/RcrRZS5Y/Az10OU968XzrSx9rUI=",
"recordedFileInputs": {
"@@//third_party/rust/Cargo.lock": "fa9d7f659ffbfb7f5554de4a7e653d3bd900f0a75b22ef4c0853b98c6ede5548",
@@ -2694,7 +2694,7 @@
},
"@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": {
"general": {
- "bzlTransitiveDigest": "/lCPEYwxLjcUUzuJlryJJyzteuW5rxwGQ86cPPkUL9M=",
+ "bzlTransitiveDigest": "c2lNdp995IIrl9Z4g/h/k41gXEU73T2zNYqAIatm4/o=",
"usagesDigest": "Pr9/2PR9/ujuo94SXikpx+fg31V4bDKobC10YJu+z5I=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
diff --git a/third_party/rules_rust/musl.patch b/third_party/rules_rust/musl.patch
new file mode 100644
index 0000000..81fce04
--- /dev/null
+++ b/third_party/rules_rust/musl.patch
@@ -0,0 +1,177 @@
+commit e48fd8490d497fdfa6d5a7f8a3af63cc8c6db538
+Author: Jan Schär <jan@monogon.tech>
+Date: Thu Jul 3 16:03:25 2025 +0000
+
+ Use musl toolchain and add loader_wrapper
+
+ This patches rules_rust to use the Rust toolchain built for musl instead
+ of glibc, and adds a wrapper to locate the dynamic loader. What we
+ achieve with this is that we no longer need a specific loader installed
+ at an absolute path, which makes the build less dependent on the host
+ OS.
+
+ The tools are linked dynamically and need libc.so and libgcc_s.so.1. At
+ least rustc cannot be linked statically, because proc macros are loaded
+ with dlopen. When building proc macros, we need to add library paths
+ containing the two .so files such that lld can find them.
+
+ Usually, musl would be installed at /lib/ld-musl-x86_64.so.1, but we
+ can't do that. Instead, the binaries are renamed to {name}_real and a
+ wrapper script is placed there instead. The wrapper runs musl as a
+ dynamic loader, passing it the path to the real binary.
+
+diff --git a/rust/platform/triple.bzl b/rust/platform/triple.bzl
+index 87cd6242..766bc08e 100644
+--- a/rust/platform/triple.bzl
++++ b/rust/platform/triple.bzl
+@@ -138,7 +138,7 @@ def get_host_triple(repository_ctx, abi = {}):
+ prefix = "{}-unknown-linux".format(arch)
+ return triple("{}-{}".format(
+ prefix,
+- abi.get(prefix, "gnu"),
++ abi.get(prefix, "musl"),
+ ))
+
+ if "mac" in repository_ctx.os.name:
+diff --git a/rust/private/loader_wrapper.sh b/rust/private/loader_wrapper.sh
+new file mode 100755
+index 00000000..29805d8d
+--- /dev/null
++++ b/rust/private/loader_wrapper.sh
+@@ -0,0 +1,18 @@
++#!/bin/sh
++
++# Running rustc and other tools directly doesn't work, because that would
++# require musl to be installed at /lib/ld-musl-x86_64.so.1, but we can't install
++# things at absolute paths. Instead, this script calls musl as a dynamic loader,
++# giving it the path to the real binary which has been renamed to {name}_real.
++
++set -eu
++
++selfDir="$(dirname "$0")"
++rootPath="$(dirname "$selfDir")"
++
++exec "${rootPath}/sysroot/usr/lib/libc.so" \
++ "--library-path=${rootPath}/sysroot/usr/lib" \
++ "--argv0" "$0" \
++ "--" \
++ "${0}_real" \
++ "$@"
+diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
+index 27c08e78..0761c34c 100644
+--- a/rust/private/repository_utils.bzl
++++ b/rust/private/repository_utils.bzl
+@@ -41,12 +41,16 @@ filegroup(
+ name = "rustc_lib",
+ srcs = glob(
+ [
++ "bin/*_real",
+ "bin/*{dylib_ext}",
+ "lib/*{dylib_ext}*",
+ "lib/rustlib/{target_triple}/codegen-backends/*{dylib_ext}",
+ "lib/rustlib/{target_triple}/bin/rust-lld{binary_ext}",
+ "lib/rustlib/{target_triple}/lib/*{dylib_ext}*",
+ "lib/rustlib/{target_triple}/lib/*.rmeta",
++ "sysroot/usr/lib/libc.so",
++ "sysroot/usr/lib/libgcc_s.so*",
++ "sysroot/usr/lib/libunwind.so*",
+ ],
+ allow_empty = True,
+ ),
+diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
+index d78c2890..fbc00591 100644
+--- a/rust/private/rustc.bzl
++++ b/rust/private/rustc.bzl
+@@ -1012,6 +1012,12 @@ def construct_arguments(
+ if linker_script:
+ rustc_flags.add(linker_script, format = "--codegen=link-arg=-T%s")
+
++ if is_exec_configuration(ctx):
++ # This is needed when building proc macros, for lld to find libgcc_s.so
++ # and libc.so. This must be before the Rust standard library paths so
++ # lld uses libc.so instead of the libc.a shipped by Rust.
++ rustc_flags.add("-L", "native={}/sysroot/usr/lib".format(tool_path.rsplit("/", 2)[0]))
++
+ # Tell Rustc where to find the standard library (or libcore)
+ rustc_flags.add_all(toolchain.rust_std_paths, before_each = "-L", format_each = "%s")
+ rustc_flags.add_all(rust_flags, map_each = map_flag)
+diff --git a/rust/repositories.bzl b/rust/repositories.bzl
+index a5df3842..323a26ae 100644
+--- a/rust/repositories.bzl
++++ b/rust/repositories.bzl
+@@ -42,13 +42,13 @@ load_arbitrary_tool = _load_arbitrary_tool
+ DEFAULT_TOOLCHAIN_TRIPLES = {
+ "aarch64-apple-darwin": "rust_macos_aarch64",
+ "aarch64-pc-windows-msvc": "rust_windows_aarch64",
+- "aarch64-unknown-linux-gnu": "rust_linux_aarch64",
++ "aarch64-unknown-linux-musl": "rust_linux_aarch64",
+ "powerpc64le-unknown-linux-gnu": "rust_linux_powerpc64le",
+ "s390x-unknown-linux-gnu": "rust_linux_s390x",
+ "x86_64-apple-darwin": "rust_macos_x86_64",
+ "x86_64-pc-windows-msvc": "rust_windows_x86_64",
+ "x86_64-unknown-freebsd": "rust_freebsd_x86_64",
+- "x86_64-unknown-linux-gnu": "rust_linux_x86_64",
++ "x86_64-unknown-linux-musl": "rust_linux_x86_64",
+ }
+
+ _COMPACT_WINDOWS_NAMES = True
+@@ -349,6 +349,33 @@ def rust_repositories(**kwargs):
+
+ rust_register_toolchains(**kwargs)
+
++def load_sysroot(ctx, target_triple):
++ # Download the sysroot. We only need libc.so and libunwind.so from this;
++ # they are runtime dependencies of rustc, cargo, etc.
++ ARTIFACTS_VERSION = "20250603"
++ if target_triple.arch == "x86_64":
++ ctx.download_and_extract(
++ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-x86_64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
++ sha256 = "ba0fce4438de6d4dbc1678d2e672f377d30327d24e10b0de09f090032d34c1ba",
++ output = "sysroot",
++ )
++ elif target_triple.arch == "aarch64":
++ ctx.download_and_extract(
++ ["https://monogon-llvm-toolchain.storage.googleapis.com/%s/sysroot-aarch64-unknown-linux-musl.tar.zst" % ARTIFACTS_VERSION],
++ sha256 = "0cee4d5c1d24a9e9e2d1d12fd5e05ea1afcf69ea0b5d43979290988143ce1a0f",
++ output = "sysroot",
++ )
++ else:
++ fail("missing sysroot for architecture {}".format(target_triple.arch))
++ # Rust uses either libgcc_s.so or libunwind.so for unwinding. Both have the
++ # same interface for unwinding, so we can make libgcc_s.so a symlink to
++ # libunwind.so and it will work. This could potentially break in unknown
++ # ways, but it didn't break so far. Unwinding was tested by inserting a
++ # panic in a proc macro, which worked as expected. This avoids us needing to
++ # either build libgcc_s.so or build Rust configured to use libunwind.so.
++ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so.1")
++ ctx.symlink("sysroot/usr/lib/libunwind.so.1.0", "sysroot/usr/lib/libgcc_s.so")
++
+ _RUST_TOOLCHAIN_REPOSITORY_ATTRS = {
+ "allocator_library": attr.string(
+ doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
+@@ -539,6 +566,13 @@ def _rust_toolchain_tools_repository_impl(ctx):
+ repro[key] = getattr(ctx.attr, key)
+ repro["sha256s"] = sha256s
+
++ load_sysroot(ctx = ctx, target_triple = exec_triple)
++ loader_wrapper = ctx.read(Label("//rust/private:loader_wrapper.sh"))
++ ctx.rename("bin/cargo", "bin/cargo_real")
++ ctx.file("bin/cargo", loader_wrapper)
++ ctx.rename("bin/rustc", "bin/rustc_real")
++ ctx.file("bin/rustc", loader_wrapper)
++
+ return repro
+
+ rust_toolchain_tools_repository = repository_rule(
+@@ -928,6 +962,11 @@ def _rustfmt_toolchain_tools_repository_impl(repository_ctx):
+ repro[key] = getattr(repository_ctx.attr, key)
+ repro["sha256s"] = sha256s
+
++ load_sysroot(ctx = repository_ctx, target_triple = exec_triple)
++ loader_wrapper = repository_ctx.read(Label("//rust/private:loader_wrapper.sh"))
++ repository_ctx.rename("bin/rustfmt", "bin/rustfmt_real")
++ repository_ctx.file("bin/rustfmt", loader_wrapper)
++
+ return repro
+
+ rustfmt_toolchain_tools_repository = repository_rule(