3

I have a Rust library that exposes few functions for FFI. I assume I have to set crate-type to cdylib - as I want to call these functions from Ruby and PHP (f.i. via ffi ruby gem). But I have trouble crosscompiling it from OSX to Linux. I tried to follow some tutorials that use musl libc - this is for static lib but I haven't found anything else.

So the linker is defined this way:

# .cargo/config
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

And I tried to compile it with:

cargo build --release --target x86_64-unknown-linux-musl

But there's immediate error:

error: cannot produce cdylib for `my-crate-name` as the target `x86_64-unknown-linux-musl` does not support these crate types

My question is: what target/linker pair could be used for crosscompiling cdylib? Why musl doesn't support theses crate types? Is it even possible at all?

Ernest
  • 8,701
  • 5
  • 40
  • 51

1 Answers1

7

Questions

I have a Rust library that exposes few functions for FFI. Therefore I must set crate-type to cdylib.

From where did you get this information? You can create either dynamic library (.so) or a static library (.a). Alex has a repository with lot of examples: rust-ffi-examples.

musl is used in situations where you do want to create a binary, which is statically linked and has no dependencies at all (staticlib). Everything's inside binary. You throw it at the Linux box and it will work.

Dynamic linking is used in situations where you know that all the dependencies will be met, you want smaller binaries, etc. (cdylib). But, you have to be sure that the dependencies are really there otherwise it won't work.

Cross compilation

I usually don't bother with cross compilation, because it can be very tricky if you need to dynamically link against other Linux libraries. For these cases, I've got:

  • Linux installed in the VMware Fusion,
  • Docker for Mac installed with Linux image inside.

There're many ways how to achieve what you want. See @Shepmaster comment: use CI and upload build artifacts somewhere.

Do you really need cross compilation? Isn't there any other way how to achieve your goal? Avoid it when possible.

Dymamic library

Toolchain

$ brew tap SergioBenitez/osxct
$ brew install x86_64-unknown-linux-gnu
$ rustup target add x86_64-unknown-linux-gnu

Add following lines to the ~/.cargo/config:

[target.x86_64-unknown-linux-gnu]
linker = "x86_64-unknown-linux-gnu-gcc"

Sample Rust library

Cargo.toml content:

[package]
name = "sample"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]

src/lib.rs content:

#[no_mangle]
pub extern fn hello() {
    println!("Rust here");
}

Compile & check

Compile with:

$ cargo build --release --target x86_64-unknown-linux-gnu

Check the output:

$ 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

Check library symbols:

x86_64-unknown-linux-gnu-nm -D target/x86_64-unknown-linux-gnu/release/libsample.so | grep hello
0000000000003900 T hello

Test on the Linux box

Copy target/x86_64-unknown-linux-gnu/release/libsample.so to your Linux box.

Manual loading

sample.c content:

#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);
}

Compile with gcc -rdynamic -o sample sample.c -ldl and run it:

$ ./sample
Rust here

Check that it's dynamically 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)

Dynamic linking

sample.c content:

extern void hello(void);

int main(int argc, char **argv) {
    hello();
}

Compile with gcc sample.c -o sample -lsample -L. and run it:

$ LD_LIBRARY_PATH=. ./sample
Rust here

Check that it's dynamically 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)

Static library

Toolchain

$ rustup target add x86_64-unknown-linux-musl
$ brew install filosottile/musl-cross/musl-cross

Add following lines to the ~/.cargo/config:

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

Sample Rust library

Cargo.toml content:

[package]
name = "sample"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["staticlib"]

src/lib.rs content:

#![crate_type = "staticlib"]

#[no_mangle]
pub extern fn hello() {
    println!("Rust here");
}

Compile

Compile with:

$ cargo build --release --target x86_64-unknown-linux-musl

Test on the Linux box

Copy target/x86_64-unknown-linux-musl/release/libsample.a to your Linux box.

sample.c content:

extern void hello(void);

int main(int argc, char **argv) {
    hello();
}

Compile with gcc sample.c libsample.a -o sample and run it:

$ ./sample
Rust here

Check that it's statically linked:

$ ldd ./sample
    statically linked
zrzka
  • 20,249
  • 5
  • 47
  • 73
  • 1
    *I usually don't bother with cross compilation* — the true answer. Even easier, just use a CI provider that supports Windows / Linux / macOS and build there, uploading the binaries somewhere. – Shepmaster Aug 28 '19 at 15:36
  • This was very, VERY helpful and even if in the end (maybe) I won't use cross compilation by your recommendation, your examples helped me to learn a lot. – Ernest Aug 29 '19 at 17:00
  • 1
    There was a typo, check the last edit (.so -> .a in case of static). Also check this [repo](https://github.com/alexcrichton/rust-ffi-examples), examples of Rust & FFI. – zrzka Aug 29 '19 at 17:12
  • 1
    @zrzka I was able to successfully compile `cdylib` library and use it with dynamic linking from Ruby and PHP - which was my actual goal. As for the crate type, I assumed this was the right choice based on: https://doc.rust-lang.org/reference/linkage.html#linkage – Ernest Aug 29 '19 at 18:53