如何编译一个包含本地依赖的Rust项目的静态musl二进制文件?

15

我有一个依赖于Hyper和Diesel的项目,因此依赖于本机库OpenSSL和libpq。该项目使用编译器插件,因此需要在nightly Rust上构建。

我目前尝试在Docker容器中构建。 我已经安装了MUSL libc和带有前缀"/usr/local/musl"的库,并运行以下命令:cargo(不确定某些选项是否冗余,我对编译器链不太熟悉,也不确定它们最终是否传递给链接器,但是我必须尝试一下,对吧。)

LDFLAGS="-static -L/usr/local/musl/lib" \
LD_LIBRARY_PATH=/usr/local/musl/lib:$LD_LIBRARY_PATH \
CFLAGS="-I/usr/local/musl/include" \
PKG_CONFIG_PATH=/usr/local/musl/lib/pkgconfig \
cargo build --release --target=x86_64-unknown-linux-musl

当我运行ldd命令查看生成的文件时,会显示如下内容:

$ ldd server
linux-vdso.so.1 (0x00007fffb878e000)
libpq.so.5 => /usr/local/musl/lib/libpq.so.5 (0x00007f4d730e7000)
libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f4d72e82000)
libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f4d72a85000)
libc.so => /usr/local/musl/lib/libc.so (0x00007f4d727f6000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4d725f2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d72246000)
/lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x000055e2124a2000)

所有这些都是动态链接库,有些甚至是带有“x86_64-linux-gnu”链的!出了什么问题?

我可以创建静态链接的、简单的纯Rust项目而没有任何问题。 ldd 表明它们是静态链接的,并且它们可以运行而没有问题,不像我遇到问题的可执行文件。

当我在Cargo中使用了 --verbose ,我得到了以下实际构建可执行文件的 rustc 命令:http://pastebin.com/ywv0zNBK (哎呀,那个有一个自定义的 outdir-Z print-link-args,由我添加)。添加了 print-link-args 标志后,我得到了以下链接器命令:http://pastebin.com/Aw43qd7h

我怎样才能让 cargo 或者 rustc 相信我想要一个静态二进制文件呢?


1
我非常确定答案是:“你不容易做到”。您需要将libpq和OpenSSL重新构建为静态库(并链接到MUSL?)。然后,您必须找出如何更改相应的Rust库以链接到静态版本。我大约70%确定,但不完全确定。 - Shepmaster
事实是,我已经使用MUSL重新构建了它们,在前缀/usr/local/musl下。我试图通过正确设置环境来获取相应的库以链接到静态版本,但显然我失败了。你所说的方式听起来像是我需要修改库本身,这是你的意思吗? - GolDDranks
我的意思是,一旦你编译了C库的MUSL版本(做得很好!),你就必须研究每个Rust绑定,以确定如何更改它们链接的方式和目标。例如,openssl有环境变量 - Shepmaster
感谢您提供有关环境变量的指针!那是一个至关重要的线索。 - GolDDranks
我的朋友,也许你没有查找正确的目录。你的结果将在target/x86_64-unknown-linux-musl/release/中。 - Literadix
3个回答

18
问题在于,对于每个提供本地依赖项的木箱(例如 OpenSSL),都有负责向 Cargo 和 rustc 传达构建和链接选项的 build.rs 构建脚本。 (例如:它们打印出类似于 cargo:rustc-link-lib=static=ssl 的内容,然后 Cargo 读取并相应地执行。)
因此,仅设置“标准”GCC环境变量几乎没有任何效果。 您必须单独检查每个 build.rs,以了解如何强制该精确木箱向货物传递其选项。 对于 OpenSSL,其 env vars 如 OPENSSL_DIR、OPENSSL_STATIC 等。
另一个障碍是,如果您使用编译器插件,则它们也可以使用目标三元组进行编译(至少docker_codegen)。 另一方面,在编译过程中动态链接它们。 这意味着不仅必须正确链接静态库,还必须具有 Musl libc.so 等主机类型的动态库,并正确设置(LD_LIBRARY_PATH 等)。
我制作了一个详细注释的 Dockerfile,其中包含一些本地依赖项的静态构建项目。 它可能对其他人也有所帮助。

https://gitlab.com/rust_musl_docker/image


请问您能否重新发布这个Docker文件,因为链接已经失效了。 - dessalines
啊,抱歉,这个项目已经转移到 GitLab 上了。我会更新链接的。 - GolDDranks

18

如果您想静态链接一个没有本地依赖的Rust程序,那么这将更加容易:

$ rustup target add x86_64-unknown-linux-musl
$ cargo build --release --target=x86_64-unknown-linux-musl

5
无法找到 OpenSSL,无法编译任何东西。 - nikoss
1
@nikoss,假设你使用的是Linux系统,请尝试启用openssl crate的“vendored”功能,并确保你已经安装了musl-gcc。 - kyku
1
这对我来说有效。此外,我确实需要安装musl-tools。这里是一个展示在GitHub Action上进行静态构建的pull request - Eugene Yokota

1
我遇到了与ldd和GCC相同的问题。 musl目标生成在不同的目录中,而不是在target/release/...中,而是在target/x86_64-unknown-linux-musl/release/...中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接