From: Sunil Dora Date: Mon, 13 Apr 2026 07:04:57 +0000 (-0700) Subject: rust: enable fully static linking with TCLIBC=musl X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=75409c60e9e63fdcbb9d4f54130052991362ec08;p=thirdparty%2Fopenembedded%2Fopenembedded-core.git rust: enable fully static linking with TCLIBC=musl 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 Signed-off-by: Sunil Dora Signed-off-by: Mathieu Dubois-Briand --- 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"