Bindgen: `ld: symbol(s) not found for architecture arm64`

Hey everyone,

I'm trying to generate bindings for the libical project in Rust via CMake and Bindgen. My platform is MacOS 14.3.1 arm64, rustc version 1.76.0, and I'm trying to build for my local macOS.

I followed the Bindgen tutorial and it worked fine, but when I tried to apply it to my use case I started running into issues.

For context, the libical source lives at $PROJECT_ROOT/libical. The project builds successfully, and I'm able to see the expected functions in the generated bindings.rs file. However, when trying to build and run my lib.rs tests, things break down and the project does not compile.

The two compilation errors I receive when running cargo test:

linking with cc
error: linking with `cc` failed: exit status: 1
  |
  = note: env -u IPHONEOS_DEPLOYMENT_TARGET -u TVOS_DEPLOYMENT_TARGET LC_ALL="C" PATH="...
undefined symbols for arch arm64
= note: Undefined symbols for architecture arm64:
            "_icalrecurrencetype_from_string", referenced from:
                libical_sys::tests::test::heaee76a4405841fa in libical_sys-d5abc1bb0890726a.2sgdgyytue3hyrnx.rcgu.o
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

What follows are my files:

wrapper.h
#include "libical/ical.h"
build.rs
extern crate bindgen;
extern crate cmake;

use std::env;
use std::path::PathBuf;

use cmake::Config;

fn main() {
    let dst = Config::new("libical")
        .env("CMAKE_INSTALL_DATAROOTDIR", "./shared")
        .define("CMAKE_BUILD_TYPE", "Release")
        .define("ICAL_GLIB", "False")
        .define("ENABLE_GTK_DOC", "OFF")
        .build();

    println!(
        "cargo:rustc-link-search=native={}",
        dst.join("lib").display()
    );

    // 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()
        // The input header we would like to generate
        // bindings for.
        .clang_arg(format!("-I{}/include", env::var("OUT_DIR").unwrap()))
        .clang_arg(format!("-L{}/lib", env::var("OUT_DIR").unwrap()))
        .header("wrapper.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        // 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.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}
lib.rs
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

extern crate alloc;

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

#[cfg(test)]
mod tests {
    use alloc::ffi::CString;
    use std::os::raw::c_char;

    use super::*;

    #[test]
    fn test() {
        let rrule = CString::new("FREQ=YEARLY;BYDAY=SU,WE").unwrap();

        unsafe {
            let recur = icalrecurrencetype_from_string(rrule.as_ptr() as *const c_char);
            println!("{:#?}", recur.freq);
        }
    }
}

What am I doing wrong? How can I link the appropriate files to ensure the project compiles for testing?

I don't know about MacOS, but I think your build.rs forgot to link to the library, i.e. you only have a cargo:rustc-link-search directive, but you are missing cargo:rustc-link-lib directive.

I added the directive based on your suggestion, but it didn't change the compilation errors. Please note that I'm programmatically generating this path and not setting it manually.

I've pulled the following from the generated output file:

cargo:rustc-link-lib=/Users/sachanganesh/.../libical-sys/target/debug/build/libical-sys-96af96a9dd05bced/out/lib

I also tried other permutations like omitting the trailing /lib from the path, but it didn't change the result.

Am I doing this as expected?

You have to pass the name of the library without lib prefix and .a suffix to cargo:rustc-link-lib. The linker will then look for the library in the path(s) specified by cargo:rustc-link-search.

1 Like

Thank you! That solved my issues.

For posterity's sake here is the final build.rs:

extern crate bindgen;
extern crate cmake;

use std::env;
use std::path::PathBuf;

use cmake::Config;

fn main() {
    let dst = Config::new("libical")
        .env("CMAKE_INSTALL_DATAROOTDIR", "./shared")
        .define("CMAKE_BUILD_TYPE", "Release")
        .define("ICAL_GLIB", "False")
        .define("ENABLE_GTK_DOC", "OFF")
        .build();

    println!(
        "cargo:rustc-link-search=native={}",
        dst.join("lib").display()
    );

    println!("cargo:rustc-link-lib=static={}", "ical");

    // 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()
        // 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::new()))
        // 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.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.