]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core.git/commitdiff
rust: enable fully static linking with TCLIBC=musl
authorSunil Dora <sunilkumar.dora@windriver.com>
Mon, 13 Apr 2026 07:04:57 +0000 (00:04 -0700)
committerRichard Purdie <richard.purdie@linuxfoundation.org>
Thu, 16 Apr 2026 10:09:38 +0000 (11:09 +0100)
Fixes [YOCTO #16076]

Rust binaries built with TCLIBC=musl and
-C target-feature=+crt-static were still dynamically linked.
Fix this by addressing three issues:

1) Set crt-static-respected in the generated musl target spec
   so rustc honors +crt-static. [1]

2) Add the target sysroot library path to the linker flags so
   libunwind.a can be found.

3) Use LLVM libunwind for musl:
   - GNU libunwind does not provide static libraries in OE
     and lacks required _Unwind_* symbols on some architectures [2]
   - libgcc_eh depends on pthread and cannot be used for fully
     static linking with musl
   - LLVM libunwind provides the required symbols without
     additional dependencies
   Install LLVM libunwind from libcxx and switch libstd-rs
   to depend on libcxx for musl.

Also remove the obsolete DEPENDS:remove:riscv32/riscv64 = "libunwind"
lines added in 2021 when riscv musl support was still being patched.
LLVM libunwind supports both riscv32 and riscv64 - verified locally.
riscv32 support was upstreamed at [3].

Add a selftest to verify that produced binaries are statically linked.

[1] https://github.com/rust-lang/rust/blob/main/compiler/rustc_target/src/spec/mod.rs
[2] https://github.com/libunwind/libunwind/issues/761
[3] https://github.com/llvm/llvm-project/commit/b17d464

Reported-by: Nick Owens <nick.owens@eero.com>
Signed-off-by: Sunil Dora <sunilkumar.dora@windriver.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.lock [new file with mode: 0644]
meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.toml [new file with mode: 0644]
meta-selftest/recipes-devtools/rust/rust-static-musl-test/src/main.rs [new file with mode: 0644]
meta-selftest/recipes-devtools/rust/rust-static-musl-test_0.1.bb [new file with mode: 0644]
meta/classes-recipe/rust-common.bbclass
meta/classes-recipe/rust-target-config.bbclass
meta/lib/oeqa/selftest/cases/rust.py
meta/recipes-devtools/clang/libcxx_git.bb
meta/recipes-devtools/rust/libstd-rs_1.94.1.bb

diff --git a/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.lock b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.lock
new file mode 100644 (file)
index 0000000..cb48802
--- /dev/null
@@ -0,0 +1,5 @@
+version = 3
+
+[[package]]
+name = "rust-static-musl-test"
+version = "0.1.0"
diff --git a/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.toml b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.toml
new file mode 100644 (file)
index 0000000..d707130
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "rust-static-musl-test"
+version = "0.1.0"
+edition = "2021"
diff --git a/meta-selftest/recipes-devtools/rust/rust-static-musl-test/src/main.rs b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/src/main.rs
new file mode 100644 (file)
index 0000000..85dcaf9
--- /dev/null
@@ -0,0 +1,3 @@
+fn main() {
+    println!("static-musl-ok");
+}
diff --git a/meta-selftest/recipes-devtools/rust/rust-static-musl-test_0.1.bb b/meta-selftest/recipes-devtools/rust/rust-static-musl-test_0.1.bb
new file mode 100644 (file)
index 0000000..442c24c
--- /dev/null
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: MIT
+# Minimal Rust binary to test static musl linking (bug 16076)
+
+SUMMARY = "Minimal Rust binary for static musl linking regression test"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+SRC_URI = "file://Cargo.toml \
+           file://Cargo.lock \
+           file://src/main.rs \
+          "
+
+S = "${UNPACKDIR}"
+CARGO_SRC_DIR = ""
+
+inherit cargo
+
+COMPATIBLE_HOST = ".*-musl.*"
+
+SSTATE_SKIP_CREATION = "1"
index 34bb2377cf41895830f756ab685adbe0ebe829eb..057aeda67a11e1769cd611d5b0d5d6d2501351b2 100644 (file)
@@ -161,6 +161,7 @@ WRAPPER_TARGET_CC = "${CC}"
 WRAPPER_TARGET_CXX = "${CXX}"
 WRAPPER_TARGET_CCLD = "${CCLD}"
 WRAPPER_TARGET_LDFLAGS = "${LDFLAGS}"
+WRAPPER_TARGET_LDFLAGS:append:libc-musl = " -L${STAGING_DIR_TARGET}${libdir}"
 WRAPPER_TARGET_EXTRALD = ""
 # see recipes-devtools/gcc/gcc/0018-Add-ssp_nonshared-to-link-commandline-for-musl-targe.patch
 # we need to link with ssp_nonshared on musl to avoid "undefined reference to `__stack_chk_fail_local'"
index 2e83cf5aa7aa88b282a96681da190fee7446b7a8..3469de214220c822f8973e15fd531e623940d0ff 100644 (file)
@@ -429,6 +429,8 @@ def rust_gen_target(d, thing, wd, arch):
     tspec['has-thread-local'] = True
     tspec['position-independent-executables'] = True
     tspec['panic-strategy'] = d.getVar("RUST_PANIC_STRATEGY")
+    if "musl" in tspec['llvm-target']:
+        tspec['crt-static-respected'] = True
 
     # write out the target spec json file
     with open(wd + rustsys + '.json', 'w') as f:
index 7614941661d7e1c1b8525513503655d800d2ce25..72ffa5f2345a52f8aa1b027134e319ec9334950a 100644 (file)
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: MIT
+import os
 import subprocess
 import time
 from oeqa.core.decorator import OETestTag
@@ -144,3 +145,32 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
             test_results = parse_results(resultlog)
             for test in test_results:
                 self.ptest_result(ptestsuite, test, test_results[test])
+
+# Regression test for bug 16076 - verify static linking with TCLIBC=musl
+class RustStaticMuslTest(OESelftestTestCase):
+
+    RECIPE = "rust-static-musl-test"
+
+    def setUp(self):
+        super().setUp()
+        self.write_config(
+            'TCLIBC = "musl"\n'
+            'RUSTFLAGS:append:pn-%s = " -C target-feature=+crt-static"\n' % self.RECIPE
+        )
+        result = bitbake(self.RECIPE, ignore_status=True)
+        self.assertEqual(result.status, 0,
+            msg="bitbake %s failed:\n%s" % (self.RECIPE, result.output))
+
+    def test_static_musl_linking(self):
+        workdir = get_bb_var("WORKDIR", self.RECIPE)
+        cargo_target_subdir = get_bb_var("CARGO_TARGET_SUBDIR", self.RECIPE)
+        pn = get_bb_var("PN", self.RECIPE)
+        binary = os.path.join(workdir, "build", "target",
+                              cargo_target_subdir, pn)
+
+        result = runCmd("file %s" % binary, ignore_status=True)
+        self.assertIn("ELF", result.output,
+            msg="Not an ELF binary: %s" % result.output)
+        self.assertIn("statically linked", result.output,
+            msg="Binary is not statically linked. Regression of bug 16076.\n"
+                "file output: %s" % result.output)
index 42b2c91e43de14f2f41cebec2942e771a2150502..dff063ad8b75a9f25add29a2fcd6f9ec8de7d42e 100644 (file)
@@ -31,6 +31,11 @@ LIC_FILES_CHKSUM = "file://libcxx/LICENSE.TXT;md5=55d89dd7eec8d3b4204b680e27da39
 OECMAKE_TARGET_COMPILE = "cxxabi cxx"
 OECMAKE_TARGET_INSTALL = "install-cxxabi install-cxx"
 
+# LLVM libunwind.a needed for static Rust musl builds.
+# GNU libunwind never produces .a on musl so no collision risk.
+OECMAKE_TARGET_COMPILE:append:libc-musl = " unwind"
+OECMAKE_TARGET_INSTALL:append:libc-musl = " install-unwind"
+
 CC = "${CCACHE}${HOST_PREFIX}clang ${HOST_CC_ARCH}${TOOLCHAIN_OPTIONS}"
 CXX = "${CCACHE}${HOST_PREFIX}clang++ ${HOST_CC_ARCH}${TOOLCHAIN_OPTIONS}"
 BUILD_CC = "${CCACHE}clang ${BUILD_CC_ARCH}"
index 8af93bec579c4074091b299b5d33ca38259d10a8..2a5e977a5e23b5111e62e3209d4fba840402d35c 100644 (file)
@@ -17,10 +17,7 @@ inherit cargo
 
 CVE_PRODUCT = "rust"
 
-DEPENDS:append:libc-musl = " libunwind"
-# rv32 does not have libunwind ported yet
-DEPENDS:remove:riscv32 = "libunwind"
-DEPENDS:remove:riscv64 = "libunwind"
+DEPENDS:append:libc-musl = " libcxx"
 
 # Embed bitcode in order to allow compiling both with and without LTO
 RUSTFLAGS += "-Cembed-bitcode=yes"