rustlinkerlinker-errorsffi

Rust Linker is not finding included FFI library during cucumber test compile


So we have a cucumber test crate that tests an FFI crate. We include the FFI library in the cargo.toml file with :

ffi_lib = { path = "../base/ffi_lib" }

Then its linked into the code via:

#[link(name = "ffi_lib")]
#[allow(dead_code)]
extern "C" {
    pub fn create_vector(tag: TTypeTag) -> *mut TVector;

Now this code works on Rust nightly-2024-08-03 and earlier, but as soon as you increase the version to nightly-2024-08-04 or later it breaks with:

error: linking with `cc` failed: exit status: 1
 note:  "cc" "/var/folders/47/s7....{}
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld: warning: ignoring duplicate libraries: '-liconv'
          ld: library 'ffi_lib' not found
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

I am struggling to find what rustc versions are used by these nightly version or docs about what could have changed. For what its worth this was done on a Mac M1, but fails on Unix CI as well with the same issue, so I doubt its a OSX only issue.

I can also see the ffi_lib library is built, and all its files are in the target folder.

Edit: Running cargo +nightly-2025-05-01 test --all-features --release -v I can see that rustc is adding in the -lffi_lib

Edit2: project structure looks as follows:

root
+---base
     +---ffi_lib
                +---cargo.toml
+---integration_tests
     +---cargo.toml 

The test are in integration_tests The cargo.toml of ffi_lib contains this:

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

Solution

  • You are relying on incidental linking behavior that was not guaranteed. Before Rust 1.82, Cargo provided /target/.../deps as a library search path by default, but a change in 1.82 removed that.

    From this issue from someone with a similar problem afterwards:

    I don't think this was supposed to work out of the box. Either you should make my_lib a cdylib (requiring you to build the cdylib from within the build script rather than specifying it as dependency, at least until artifact dependencies are stable) and link it like a C library (which includes adding it to the linker search path from a build script) or you should link it like a regular rust library and avoid #[link(name = "my_lib.dll")] extern "C" {}.

    So you should define a build script with the search path as you would with any other C library.

    On that same note, adding ffi_lib = { path = "../base/ffi_lib" } to your Cargo.toml dependencies is similarly relying on behavior that is not guaranteed. You are using it to ensure libffi_lib.so is built which incidentally ends up working because the Rust compiler will produce all crate types, but Cargo dependencies are for Rust libraries not C/system libraries. I'm frankly surprised it is accepted at all without an "rlib" crate type and it seems if you try this same trick with a modern compiler, you'd see this warning:

    warning: The package `ffi_lib` provides no linkable target. The compiler might raise an error while compiling `integration_tests`. Consider adding 'dylib' or 'rlib' to key `crate-type` in `ffi_lib`'s Cargo.toml. This warning might turn into a hard error in the future.
    

    Just adding "rlib" to the list of crate-types wouldn't be a complete solution because with the #[link] attribute you'd essentially be compiling and linking ffi_lib into your crate twice, once as a C library and once as a Rust library. Sounds like a recipe for confusion.

    So from a combination of a few factors your code worked, but was never a supported workflow. Remove the Cargo dependency and add a build script that locates your library.