如何在全平台构建无需额外目标的最小#[no_std]二进制用于测试构建依赖
问题背景
我需要在#[no_std]环境中测试一个构建依赖。Cargo为构建依赖创建的环境是宿主环境而非目标环境,但待编译crate为#[no_std]时存在细微差异。作为集成测试的一部分,我会在磁盘上创建依赖于待测试构建依赖的最小虚拟crate,启动新Cargo进程构建该crate,以Cargo的退出状态作为测试依据。
当前核心问题:如何使用Cargo构建#[no_std]二进制,无需当前rustc安装包含外部目标(如arm...、thumb...),也不依赖执行测试的平台?
当前尝试的配置与报错
我尝试了最基础的仅需编译无需运行的#[no_std] crate配置:
Cargo.toml 配置
[package] name = "nostd" version = "0.1" build = true [build-dependencies] to_be_tested = ... [profile.dev] panic = "abort" # 无需 panic 处理逻辑
src/main.rs 代码
#![no_main] #![no_std] use core::panic::PanicInfo; #[panic_handler] fn panic(_panic: &PanicInfo<'_>) -> ! { // 永远不会被执行 loop {} }
但构建时失败,报错如下:
error: linking with `cc` failed: exit status: 1 | = note: [...] = note: ld: warning: no platform load command found in '[...]/symbols.o', assuming: macOS ld: Undefined symbols: _main, referenced from: <initial-undefines> clang: error: linker command failed with exit code 1 (use -v to see invocation)
问题本质是链接器要求存在入口函数(如main),且入口函数的签名随目标平台变化,而我需要在任意平台上完成编译,无需适配不同平台的入口签名——毕竟这个crate只需要编译成功,不需要实际运行。
解决方案
方案1:改用库类型crate跳过链接
如果测试仅需验证构建依赖逻辑,不需要验证二进制链接环节,可以将虚拟crate改为库类型:
修改Cargo.toml:
[package] name = "nostd_test" version = "0.1.0" edition = "2021" [lib] crate-type = ["rlib"] # 静态库,无需链接std [build-dependencies] to_be_tested = { path = "../path/to/your/crate" } [profile.dev] panic = "abort"
对应的src/lib.rs:
#![no_std] use core::panic::PanicInfo; #[panic_handler] fn panic(_: &PanicInfo<'_>) -> ! { loop {} }
运行cargo build --lib即可,只会编译库代码,不会触发链接操作,自然不会出现main缺失的问题。
方案2:跳过链接阶段,仅生成目标文件
如果必须使用二进制目标,可以通过Rust编译选项跳过链接,只生成目标文件:
在虚拟crate的.cargo/config.toml中添加:
[build] rustflags = ["--emit=obj"]
或者直接在命令行传递参数:
cargo build --target=$(rustc -vV | grep host | cut -d' ' -f2) --rustflags="--emit=obj"
此方式下Cargo仅完成代码编译生成目标文件,不执行链接,也就不会要求入口函数存在。
方案3:添加跨平台兼容的入口函数存根
如果必须完成链接,可以通过条件编译为不同平台提供适配的入口函数:
修改src/main.rs:
#![no_main] #![no_std] use core::panic::PanicInfo; #[panic_handler] fn panic(_panic: &PanicInfo<'_>) -> ! { loop {} } // Linux 平台入口 #[cfg(target_os = "linux")] #[link_section = ".text.startup"] #[no_mangle] pub extern "C" fn _start() -> ! { loop {} } // macOS 平台入口 #[cfg(target_os = "macos")] #[no_mangle] pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> ! { loop {} } // Windows 平台入口 #[cfg(target_os = "windows")] #[no_mangle] pub extern "system" fn WinMain(_hInstance: isize, _hPrevInstance: isize, _lpCmdLine: *const u8, _nShowCmd: u32) -> ! { loop {} } // 其他通用平台入口 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] #[no_mangle] pub extern "C" fn main() -> ! { loop {} }
该方案通过条件编译覆盖主流平台的入口要求,入口函数仅做无限循环(不会实际执行逻辑),满足链接器要求的同时,完全适配仅需编译的测试场景。
内容的提问来源于stack exchange,提问作者user2722968

