使用 cargo 和 rust 在 macOS 上为 Linux 交叉编译和 link 一个动态库 (cdylib)
Cross-compile and link a dynamic library (cdylib) on macOS for Linux with cargo and rust
我有一个 Rust 库,它为 FFI
公开了一些函数。我假设我必须将 crate-type
设置为 cdylib
- 因为我想从 Ruby 和 PHP 调用这些函数(f.i。通过 ffi
ruby gem)。
但是我无法将它从 OSX 交叉编译为 Linux。我尝试遵循一些使用 musl libc
的 tutorials - 这是针对静态库的,但我还没有找到其他任何东西。
所以链接器是这样定义的:
# .cargo/config
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
我尝试用以下方法编译它:
cargo build --release --target x86_64-unknown-linux-musl
但立即出现错误:
error: cannot produce cdylib for `my-crate-name` as the target `x86_64-unknown-linux-musl` does not support these crate types
我的问题是:什么 target/linker 对可以用于交叉编译 cdylib
?为什么 musl 不支持这些板条箱类型?有可能吗?
问题
I have a Rust library that exposes few functions for FFI
. Therefore I must set crate-type to cdylib
.
你从哪里得到这些信息的?您可以创建动态库 (.so
) 或静态库 (.a
)。 Alex 有一个包含大量示例的存储库:rust-ffi-examples.
musl 用于您确实想要创建二进制文件的情况,该二进制文件是静态 linked 并且根本没有依赖项 (staticlib
)。一切都在二进制文件中。你把它扔到 Linux 盒子里,它就会起作用。
动态 linking 用于您知道所有依赖项都将得到满足、您想要较小的二进制文件等的情况 (cdylib
)。但是,您必须确保依赖关系确实存在,否则它将无法工作。
交叉编译
我通常不关心交叉编译,因为如果您需要针对其他 Linux 库动态地 link,这可能会非常棘手。对于这些情况,我有:
- Linux安装在VMware Fusion中,
- Docker for Mac 安装了 Linux 图像。
有很多方法可以实现您想要的。请参阅@Shepmaster 评论:使用 CI 并在某处上传构建工件。
你真的需要交叉编译吗?没有其他方法可以实现您的目标吗?尽可能避免它。
动态库
工具链
$ brew tap SergioBenitez/osxct
$ brew install x86_64-unknown-linux-gnu
$ rustup target add x86_64-unknown-linux-gnu
将以下行添加到 ~/.cargo/config
:
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-unknown-linux-gnu-gcc"
示例 Rust 库
Cargo.toml
内容:
[package]
name = "sample"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
src/lib.rs
内容:
#[no_mangle]
pub extern fn hello() {
println!("Rust here");
}
编译检查
编译:
$ cargo build --release --target x86_64-unknown-linux-gnu
检查输出:
$ file target/x86_64-unknown-linux-gnu/release/libsample.so
target/x86_64-unknown-linux-gnu/release/libsample.so: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
检查库符号:
x86_64-unknown-linux-gnu-nm -D target/x86_64-unknown-linux-gnu/release/libsample.so | grep hello
0000000000003900 T hello
在Linux盒子上测试
将 target/x86_64-unknown-linux-gnu/release/libsample.so
复制到您的 Linux 框。
手动加载
sample.c
内容:
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char**argv) {
void *lib;
void (*hello)(void);
char *error;
lib = dlopen("./libsample.so", RTLD_NOW);
if (!lib) {
fprintf(stderr, "%s\n", dlerror());
exit(-1);
}
dlerror();
*(void **)(&hello) = dlsym(lib, "hello");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
dlclose(lib);
exit(-1);
}
(*hello)();
dlclose(lib);
exit(0);
}
用gcc -rdynamic -o sample sample.c -ldl
和运行编译它:
$ ./sample
Rust here
检查它是否动态 linked:
$ ldd ./sample
linux-vdso.so.1 (0x00007ffe609eb000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc7bdd69000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc7bd978000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc7be16f000)
动态linking
sample.c
内容:
extern void hello(void);
int main(int argc, char **argv) {
hello();
}
用gcc sample.c -o sample -lsample -L.
和运行编译它:
$ LD_LIBRARY_PATH=. ./sample
Rust here
检查它是否动态 linked:
$ LD_LIBRARY_PATH=. ldd ./sample
linux-vdso.so.1 (0x00007ffc6fef6000)
libsample.so => ./libsample.so (0x00007f8601ba3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f86017b2000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f86015ae000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f86013a6000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8601187000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f8600f6f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8601fd5000)
静态库
工具链
$ rustup target add x86_64-unknown-linux-musl
$ brew install filosottile/musl-cross/musl-cross
将以下行添加到 ~/.cargo/config
:
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
示例 Rust 库
Cargo.toml
内容:
[package]
name = "sample"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["staticlib"]
src/lib.rs
内容:
#![crate_type = "staticlib"]
#[no_mangle]
pub extern fn hello() {
println!("Rust here");
}
编译
编译:
$ cargo build --release --target x86_64-unknown-linux-musl
在Linux盒子上测试
将 target/x86_64-unknown-linux-musl/release/libsample.a
复制到您的 Linux 框。
sample.c
内容:
extern void hello(void);
int main(int argc, char **argv) {
hello();
}
用 gcc sample.c libsample.a -o sample
和 运行 编译它:
$ ./sample
Rust here
检查它是否是静态的 linked:
$ ldd ./sample
statically linked
我有一个 Rust 库,它为 FFI
公开了一些函数。我假设我必须将 crate-type
设置为 cdylib
- 因为我想从 Ruby 和 PHP 调用这些函数(f.i。通过 ffi
ruby gem)。
但是我无法将它从 OSX 交叉编译为 Linux。我尝试遵循一些使用 musl libc
的 tutorials - 这是针对静态库的,但我还没有找到其他任何东西。
所以链接器是这样定义的:
# .cargo/config
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
我尝试用以下方法编译它:
cargo build --release --target x86_64-unknown-linux-musl
但立即出现错误:
error: cannot produce cdylib for `my-crate-name` as the target `x86_64-unknown-linux-musl` does not support these crate types
我的问题是:什么 target/linker 对可以用于交叉编译 cdylib
?为什么 musl 不支持这些板条箱类型?有可能吗?
问题
I have a Rust library that exposes few functions for
FFI
. Therefore I must set crate-type tocdylib
.
你从哪里得到这些信息的?您可以创建动态库 (.so
) 或静态库 (.a
)。 Alex 有一个包含大量示例的存储库:rust-ffi-examples.
musl 用于您确实想要创建二进制文件的情况,该二进制文件是静态 linked 并且根本没有依赖项 (staticlib
)。一切都在二进制文件中。你把它扔到 Linux 盒子里,它就会起作用。
动态 linking 用于您知道所有依赖项都将得到满足、您想要较小的二进制文件等的情况 (cdylib
)。但是,您必须确保依赖关系确实存在,否则它将无法工作。
交叉编译
我通常不关心交叉编译,因为如果您需要针对其他 Linux 库动态地 link,这可能会非常棘手。对于这些情况,我有:
- Linux安装在VMware Fusion中,
- Docker for Mac 安装了 Linux 图像。
有很多方法可以实现您想要的。请参阅@Shepmaster 评论:使用 CI 并在某处上传构建工件。
你真的需要交叉编译吗?没有其他方法可以实现您的目标吗?尽可能避免它。
动态库
工具链
$ brew tap SergioBenitez/osxct
$ brew install x86_64-unknown-linux-gnu
$ rustup target add x86_64-unknown-linux-gnu
将以下行添加到 ~/.cargo/config
:
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-unknown-linux-gnu-gcc"
示例 Rust 库
Cargo.toml
内容:
[package]
name = "sample"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
src/lib.rs
内容:
#[no_mangle]
pub extern fn hello() {
println!("Rust here");
}
编译检查
编译:
$ cargo build --release --target x86_64-unknown-linux-gnu
检查输出:
$ file target/x86_64-unknown-linux-gnu/release/libsample.so
target/x86_64-unknown-linux-gnu/release/libsample.so: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
检查库符号:
x86_64-unknown-linux-gnu-nm -D target/x86_64-unknown-linux-gnu/release/libsample.so | grep hello
0000000000003900 T hello
在Linux盒子上测试
将 target/x86_64-unknown-linux-gnu/release/libsample.so
复制到您的 Linux 框。
手动加载
sample.c
内容:
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char**argv) {
void *lib;
void (*hello)(void);
char *error;
lib = dlopen("./libsample.so", RTLD_NOW);
if (!lib) {
fprintf(stderr, "%s\n", dlerror());
exit(-1);
}
dlerror();
*(void **)(&hello) = dlsym(lib, "hello");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
dlclose(lib);
exit(-1);
}
(*hello)();
dlclose(lib);
exit(0);
}
用gcc -rdynamic -o sample sample.c -ldl
和运行编译它:
$ ./sample
Rust here
检查它是否动态 linked:
$ ldd ./sample
linux-vdso.so.1 (0x00007ffe609eb000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc7bdd69000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc7bd978000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc7be16f000)
动态linking
sample.c
内容:
extern void hello(void);
int main(int argc, char **argv) {
hello();
}
用gcc sample.c -o sample -lsample -L.
和运行编译它:
$ LD_LIBRARY_PATH=. ./sample
Rust here
检查它是否动态 linked:
$ LD_LIBRARY_PATH=. ldd ./sample
linux-vdso.so.1 (0x00007ffc6fef6000)
libsample.so => ./libsample.so (0x00007f8601ba3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f86017b2000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f86015ae000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f86013a6000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8601187000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f8600f6f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8601fd5000)
静态库
工具链
$ rustup target add x86_64-unknown-linux-musl
$ brew install filosottile/musl-cross/musl-cross
将以下行添加到 ~/.cargo/config
:
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
示例 Rust 库
Cargo.toml
内容:
[package]
name = "sample"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["staticlib"]
src/lib.rs
内容:
#![crate_type = "staticlib"]
#[no_mangle]
pub extern fn hello() {
println!("Rust here");
}
编译
编译:
$ cargo build --release --target x86_64-unknown-linux-musl
在Linux盒子上测试
将 target/x86_64-unknown-linux-musl/release/libsample.a
复制到您的 Linux 框。
sample.c
内容:
extern void hello(void);
int main(int argc, char **argv) {
hello();
}
用 gcc sample.c libsample.a -o sample
和 运行 编译它:
$ ./sample
Rust here
检查它是否是静态的 linked:
$ ldd ./sample
statically linked