Building Rust Library for staticlib and ios target

Hi all,

I'm working with a Rust library that contains many extern "C" functions. Building the crate works fine but the generated static library (.a) does not contain any of the exported symbols (i.e. extern "C" functions). However, the generated dynamic library (.dylib) file contains all exported symbols.

Any ideas why I'm not getting the exported symbols in the static library build?

Cargo.toml

...

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

Cmd

$ cargo build --lib --target aarch64-apple-ios

Thanks

would you please show one example of the type signature of the functions that you want to export?

pub type Pointer = *mut std::ffi::c_char;

#[no_mangle]
pub extern "C" fn example_function(ptr: Pointer) -> Pointer {
    ...
}

I don't see anything wrong in the code, but I don't have any experience with apple platforms, so I can only guess. how do you link against the static library exactly, like what command line flags are used, etc.? what's the actual linker error message you see when you try to link the library?

When building the static library I use the following configurations:

config.toml

[target.aarch64-apple-ios]
ar = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar"
linker = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang"

I'm using the following command to determine the static libraries required:

cargo rustc -- --print native-static-libs

And I've applied the libs output by the command as follows:

RUSTFLAGS="-lframework=CoreFoundation -liconv -lSystem -lc -lm" cargo build --lib --target aarch64-apple-ios

I then inspect the .a file using nm but it does not contain the functions I expect and when using the library I get the error <function_name> undefined symbol, not found in image.

This is the actual repo I'm trying to build for iOS: https://github.com/radixdlt/radix-engine-toolkit/tree/main/radix-engine-toolkit.

The output of --print native-static-libs is not to be added as RUSTFLAGS, but when linking against the staticlib that was produced. You likely need to change the XCode configuration to add these linker flags.

that's strange. did you check what other symbols are missing? what's the output if you run objdump -a /path/to/library? is the correct object file archived correctly?

also, I see there's cbindgen.toml, did you check the output of cbindgen? is the function present in the generated header file?

Understood, but am I right in thinking that the symbols should be in the .a library output by the build command (targer/aarch64-apple-ios/debug/libxxx.a) or will they only appear post linking?

The #[no_mangle] functions you define should already be in the .a static library. So in your case example_function should be present. What is the linker error and what does nm target/aarch64-apple-ios/debug/libxxx.a show?

--print native-static-libs shows which dynamic libraries the .a static library depends on. Static libraries don't have a way to tell the linker what libraries they depend on unlike with dynamic libraries. This is why we have --print native-static-libs to still be able to know the dependencies.

I've tried objdump but still no luck.

I've also run cbindgen and the generated headers are as expected, it contains all the extern "C" functions.

After adding the dynamic library dependencies in XCode and running a sample app I get the error:

Failed to lookup symbol 'example_function': symbol not found.

The output of nm is:

radix_engine_toolkit.2g106qn3ufjfrmjj.rcgu.o:
                 U ___rdl_alloc
                 U ___rdl_alloc_zeroed
                 U ___rdl_dealloc
                 U ___rdl_realloc
                 U ___rg_oom
---------------- T ___rust_alloc
---------------- T ___rust_alloc_error_handler
---------------- D ___rust_alloc_error_handler_should_panic
---------------- T ___rust_alloc_zeroed
---------------- T ___rust_dealloc
---------------- D ___rust_no_alloc_shim_is_unstable
---------------- T ___rust_realloc

...

can you call the function within rust code? is there any chance the function is not compiled into the library, like guarded by a #[cfg(feature = "foo")] attribute or something?

Does nm list example_function somewhere?

I created a new lib project, imported the crate (which I have locally) and successfully called one of its methods.

Cargo.toml

[package]
name = "example"
version = "0.1.0"
edition = "2021"

[dependencies]
radix-engine-toolkit = { path = "../radix-engine-toolkit" }

main.rs

use std::ffi::CString;
use radix_engine_toolkit::prelude::build_information;

fn main() {
    // JSON string to c pointer.
    let s = String::from("{}");
    let cs = CString::new(s).unwrap();
    let mut cv: Vec<u8> = cs.into_bytes_with_nul();
    let cptr: *mut i8 = cv.as_mut_ptr() as *mut i8; 
    // Call exported function.
    let result = build_information(cptr);
    // Pointer to result.
    println!("{:?}", result);
}

Output of cargo run:

0x...

No, but it's in the libxxx.dylib that's generated (not sure if that helps).

An .a library is "just" a container for a bunch of .o files. If all else fails, you should be able to add the missing object files to the library manually, with a shell command like

ar rs target/.../your_crate.a missing_c_object_1.o missing_c_object_2.o ...

If OSX ar objects to rs, then instead do

ar r target/.../your_crate.a missing_c_object_1.o missing_c_object_2.o ...
ranlib target/.../your_crate.a

Regretfully, I cannot tell you how to glue this into your larger build.