最近在开发的过程中,遇到了一些编译问题,有跨平台编译的问题,有动态编译不兼容的问题。经过一番折腾,最后完美解决了,故记录一下这些问题的解决方案。
首先要普及一些基础概念:
Rust 编译的时候会用到两种链接方式:静态链接和动态链接。Rust 一般用静态链接,就是把所有需要的东西都打包到一个文件里,这样程序就能独立运行了。
但是对于一些系统自带的库(比如 Linux 的 libc),Rust 还是会用动态链接。这样做有个小问题:如果你的程序用了动态链接,那它运行的时候就需要系统上有对应的库文件,而且版本还得对得上。要是系统上没有这个库,或者版本不对,程序就跑不起来。
别着急,接下来我们会介绍几个解决办法,告诉你哪些方法更好用。
问题1
在本地 linux 环境中,编译的 rust 程序上传部署到 linux 服务器后,运行出错
可能会看到类似version 'GLIBC_2.33' not found (required by /path/to/your/program)的提示。 这个提示的意思就是编译的时候依赖了服务器上版本比较低或是不兼容的glibc库,从而导致了在服务器上找不到正确的glibc,于是就出错了。
解决方法也并不难:
- 升级服务器上的 glibc(风险比较高,不推荐)
使用静态编译
# 查看当前存在的工具链 rustc --print target-list # 先安装 musl 工具链 rustup target add x86_64-unknown-linux-musl # 编译时指定 musl cargo build --release --target x86_64-unknown-linux-musl
验证是否成功
编译后用这个命令检查依赖:
ldd 你的程序名如果输出显示 not a dynamic executable,恭喜你!这程序已经是完全体了,不依赖系统库了!
然而,静态编译也带来了一个问题:文件体积可能会显著增加。原本小巧的程序(例如 5MB)在静态编译后可能会膨胀到 20MB 甚至更大,尤其是当使用了复杂库(如 OpenSSL)时,这种现象更为明显。别担心,这里有一些方法可以帮助你“瘦身”:
第一:移除调试符号
调试符号文件包含代码定位信息,虽然方便调试但显著增加体积。建议正式发布时移除。
操作方式:
手动移除(通用方法)
strip 目标程序名称自动移除(Rust专属)在 Cargo.toml 添加:
[profile.release]
strip = true# 需 Rust 1.59+ 版本效果:通常可减少 30%-50% 体积
第二:编译优化配置
通过编译器优化策略实现深度瘦身
配置方案(Cargo.toml):
[profile.release]
opt-level = "z"# 最高级别体积优化
lto = true# 全局链接优化(显著增加编译时间)
codegen-units = 1# 提升优化密度
panic = "abort"# 禁用栈展开信息(注意:影响错误处理)注意事项:
建议搭配 RUSTFLAGS="-C target-cpu=native" 提升性能 编译时间可能延长 2-3 倍
第三:二进制压缩(进阶方案)
使用 UPX 进行可执行文件压缩
操作流程:
1.安装工具
# Debian/Ubuntu
apt install upx
# macOS
brew install upx
# CentOS/RHEL
yum install upx
2.执行压缩
upx --best 目标程序名称风险提示:
可能触发杀毒软件误报
首次启动增加 100-200ms 解压时间
不推荐用于高频调用的命令行工具
问题2
在rust中使用openssl依赖的时候,会有一大堆的错误场景
找不到 OpenSSL 库或头文件
错误信息示例:
“openssl/ssl.h: No such file or directory”
“Could not find OpenSSL installation”原因:系统上未安装 OpenSSL 的开发包或头文件;或者环境变量(如 OPENSSL_DIR、OPENSSL_LIB_DIR)未正确配置,导致构建脚本无法定位到 OpenSSL 的位置。
版本不兼容问题
错误信息示例: 构建失败时提示 OpenSSL 版本不匹配,或者找不到期望的符号(如
undefined reference to 'CRYPTO_num_locks'原因: 系统中安装的 OpenSSL 版本(例如 1.1 与 3.0)与 openssl-sys 或其它依赖库要求的版本不一致,导致编译或链接时符号不匹配。
对于上面的问题,解决方法也很简单,如果能替换成rustls的话,就优先替换成rustls,因为有测试表明,rustls 的性能表现比openssl更加优越,一般的依赖都可以设置一些feature来改用rustls,如:
tokio-tungstenite = { version = "0.25.0", features = ["rustls"] }但是如果实在不能更换的话,只能通过编译源码的方式来实现静态编译,可以通过依赖的vendored的features将第三方依赖的代码直接包含在项目代码仓库中,如
libsqlite3-sys = { version = "0.30.1", features = ["bundled-sqlcipher-vendored-openssl"] }这会启用openssl-sys的vendored特性,自动下载并编译OpenSSL源码。
安装构建工具 确保系统已安装编译OpenSSL所需的工具:
Linux (Ubuntu/Debian):
apt-get update
apt-get install build-essential perl pkg-configmacOS:
brew install make perlWindows:
安装Strawberry Perl(确保perl在PATH中)。
安装NASM并添加到PATH。
安装完perl,就可以成功的编译并运行了。
常见问题解决
编译错误(可能是因为缺少工具,如make、perl)。
Windows环境问题:确保Perl和NASM路径正确,且无空格或特殊字符。
最后
在 Rust 的跨平台开发中,动态链接的兼容性问题与第三方库的依赖管理是常见的挑战。通过本文的实践,我们可以总结出以下关键经验:
1.静态编译为王
优先使用 musl 目标进行全静态编译,消除对系统库(如 glibc)的依赖。虽然这会增加应用的体积,但是可以通过strip调试符号、优化编译配置(如 opt-level = "z")或者用 UPX 来减少体积膨胀的影响。
2.依赖库的灵活替换
优先选择纯 Rust 实现的库(如 rustls 替代 openssl),避免 C 绑定带来的跨平台问题。大多数依赖库已经支持通过 features 切换具体的实现(例如 tokio-tungstenite 的 rustls 特性)。 对于没得替换的 C 库(如 OpenSSL),建议启用 vendored 特性(如 openssl-sys = { features = ["vendored"] })实现源码级静态编译,避免依赖系统版本。
注:本文借鉴“云梦量化科技Rust开发工程师Rust炼金士”的文章
原文地址: https://zhuanlan.zhihu.com/p/25127947378
评论 (0)