diff mbox series

rust: enable fully static linking with TCLIBC=musl

Message ID 20260413070457.505816-1-sunilkumar.dora@windriver.com
State New
Headers show
Series rust: enable fully static linking with TCLIBC=musl | expand

Commit Message

Dora, Sunil Kumar April 13, 2026, 7:04 a.m. UTC
From: Sunil Dora <sunilkumar.dora@windriver.com>

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>
---
 .../rust/rust-static-musl-test/Cargo.lock     |  5 ++++
 .../rust/rust-static-musl-test/Cargo.toml     |  4 +++
 .../rust/rust-static-musl-test/src/main.rs    |  3 ++
 .../rust/rust-static-musl-test_0.1.bb         | 20 +++++++++++++
 meta/classes-recipe/rust-common.bbclass       |  1 +
 .../classes-recipe/rust-target-config.bbclass |  2 ++
 meta/lib/oeqa/selftest/cases/rust.py          | 30 +++++++++++++++++++
 meta/recipes-devtools/clang/libcxx_git.bb     |  5 ++++
 .../recipes-devtools/rust/libstd-rs_1.94.1.bb |  5 +---
 9 files changed, 71 insertions(+), 4 deletions(-)
 create mode 100644 meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.lock
 create mode 100644 meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.toml
 create mode 100644 meta-selftest/recipes-devtools/rust/rust-static-musl-test/src/main.rs
 create mode 100644 meta-selftest/recipes-devtools/rust/rust-static-musl-test_0.1.bb
diff mbox series

Patch

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
index 0000000000..cb488024c3
--- /dev/null
+++ b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.lock
@@ -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
index 0000000000..d7071302bd
--- /dev/null
+++ b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/Cargo.toml
@@ -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
index 0000000000..85dcaf9df5
--- /dev/null
+++ b/meta-selftest/recipes-devtools/rust/rust-static-musl-test/src/main.rs
@@ -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
index 0000000000..442c24c1da
--- /dev/null
+++ b/meta-selftest/recipes-devtools/rust/rust-static-musl-test_0.1.bb
@@ -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"
diff --git a/meta/classes-recipe/rust-common.bbclass b/meta/classes-recipe/rust-common.bbclass
index 34bb2377cf..057aeda67a 100644
--- a/meta/classes-recipe/rust-common.bbclass
+++ b/meta/classes-recipe/rust-common.bbclass
@@ -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'"
diff --git a/meta/classes-recipe/rust-target-config.bbclass b/meta/classes-recipe/rust-target-config.bbclass
index 2e83cf5aa7..3469de2142 100644
--- a/meta/classes-recipe/rust-target-config.bbclass
+++ b/meta/classes-recipe/rust-target-config.bbclass
@@ -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:
diff --git a/meta/lib/oeqa/selftest/cases/rust.py b/meta/lib/oeqa/selftest/cases/rust.py
index 7614941661..72ffa5f234 100644
--- a/meta/lib/oeqa/selftest/cases/rust.py
+++ b/meta/lib/oeqa/selftest/cases/rust.py
@@ -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)
diff --git a/meta/recipes-devtools/clang/libcxx_git.bb b/meta/recipes-devtools/clang/libcxx_git.bb
index 42b2c91e43..dff063ad8b 100644
--- a/meta/recipes-devtools/clang/libcxx_git.bb
+++ b/meta/recipes-devtools/clang/libcxx_git.bb
@@ -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}"
diff --git a/meta/recipes-devtools/rust/libstd-rs_1.94.1.bb b/meta/recipes-devtools/rust/libstd-rs_1.94.1.bb
index 8af93bec57..2a5e977a5e 100644
--- a/meta/recipes-devtools/rust/libstd-rs_1.94.1.bb
+++ b/meta/recipes-devtools/rust/libstd-rs_1.94.1.bb
@@ -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"