Search code examples
crustbindgen

Rust Bindgen to link C headers on MacOS 14 fails with lib not found


I'm working on a Rust wrapper for quaser DB that has a C API for all FFI clients.

I am using Bindgen to generate the Rust bindings and that works well meaning I got all bindings generated and I wrote some test code against them which compiles.

However, the tests don't runs because the C headerfiles rely on linking against a dylib called libqdb_api and somehow I cannot convince cargo to find this lib required for linking.

The question is: How do I configure cargo to find the dylib, which is in the project folder?

What I did:

  1. Copied the C API into the root folder of the project
    • relative path is: qdb/lib
  2. Added bindgen as a dev dependency to cargo.toml.
  3. Installed llvm via brew
  4. Set LLVM_CONFIG_PATH & LIBCLANG_PATH in build.rs
  5. Wrote a wrapper file to include headers for generate bindings
  6. Wrote a build file, see below:
use std::env;
use std::path::PathBuf;

fn main() {
    let key = "LLVM_CONFIG_PATH";
    env::set_var(key, env::var(key).unwrap_or("/opt/homebrew/opt/llvm/bin/llvm-config".to_string()));

    // https://rust-lang.github.io/rust-bindgen/requirements.html#clang
    let key = "LIBCLANG_PATH";
    env::set_var(key, env::var(key).unwrap_or("/opt/homebrew/opt/llvm/lib".to_string()));

    // Tell cargo to look for shared libraries in the specified directory
    println!("cargo:rustc-link-search=dylib=/Users/marvin/CLionProjects/quasar-rs/qdb/lib/");

    // Tell rustc to link the qdb library.
    println!("cargo:rustc-link-lib=dylib=libqdb_api");

    // Tell cargo to invalidate the built crate whenever the wrapper changes
    println!("cargo:rerun-if-changed=wrapper.h");

    // The bindgen::Builder is the main entry point to bindgen,
    // and lets you build up options for the resulting bindings.
    let bindings = bindgen::Builder::default()
        // Derive debug implementation for structs and enums
        .derive_debug(true)
        // The input header we would like to generate bindings for.
        .header("wrapper.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        // Finish the builder and generate the bindings.
        .generate()
        // Unwrap the Result and panic on failure.
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    // The resulting bindings will be written to $OUT_DIR/bindings.rs where $OUT_DIR is chosen by cargo and is something like
    // ./target/debug/build/quasar-rs..../out/.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());

    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

Cargo test results in the following error:

 = note: ld: warning: search path 'dylib=qdb/lib/' not found
          ld: library 'libqdb_api' not found
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

The linker argument from the output that I suspect:

  "-L" "dylib=qdb/lib/" "-L" "/Users/marvin/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib" "-llibqdb_api" "/Users/marvin/

Links to files, I've added:

Things I have tried:

  • Configure absolute path in build.rs - same error
  • Configure path relative to project in build.rs - same error
  • export DYLD_LIBRARY_PATH - same error
  • export DYLD_FALLBACK_LIBRARY_PATH - same error
  • Copied the lib to target and set the path to there b/c somebody posted that paths are relative to the target folder - same error

When I use the CARGO_MANIFEST_DIR env variable to construct the complete absolute path to the correct lib folder, cargo test throws another error even though the path is actually correct meaning the lib is actually there.

 ld: warning: search path 'dylib=/Users/marvin/CLionProjects/quasar-rs/qdb/lib/' not found
          ld: library 'libqdb_api' not found
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

So far, nothing worked out. I searched quite a bit online, but I only found multiple conflicting ideas with none of them working either. I am a bit baffled at this point because I never had anything like this on Linux but apparently MacOS is quite different.

How do I configure cargo correctly on MacOS to find the dylib in the project folder?.

More specifically, relative to which folder is the C linker looking for libs?


Solution

  • Apparently, the problem was that the the provided dylib was compiled for AMD64, but somehow it wasn't clear that there is no ARM-64 version. The recommended workaround is to cross compile for AMD/Intel and run the binary via Rosetta on ARM/Apple silicon.