Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS app can't link to extern "Swift" code in Release mode #166

Open
bes opened this issue Feb 11, 2023 · 11 comments
Open

iOS app can't link to extern "Swift" code in Release mode #166

bes opened this issue Feb 11, 2023 · 11 comments
Labels
good first issue Good for newcomers

Comments

@bes
Copy link
Contributor

bes commented Feb 11, 2023

When I run my iOS application in Debug mode, everything is working correctly and the code is linking properly.

But when I build my iOS app in Release mode, for App Store release, I am greeted with the following errors:

Undefined symbols for architecture arm64:
  "___swift_bridge__$OfflineReceiver$_free", referenced from:
      core::ptr::drop_in_place$LT$myproj_ios..myproj_offline..ffi..OfflineReceiver$GT$::hb5a4651c8752da54 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$OfflineReceiver$on_manifest", referenced from:
      offline::progress::run_actor::_$u7b$$u7b$closure$u7d$$u7d$::hd5afb0205e5abf69 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$OfflineReceiver$on_offline_done", referenced from:
      myproj_ios::myproj_offline::download_variant::_$u7b$$u7b$closure$u7d$$u7d$::h17a6945056206be1 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$OfflineReceiver$on_progress", referenced from:
      myproj_ios::myproj_offline::ffi::OfflineReceiver::on_progress::h80e7a91a8a0acb74 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$get_api_url", referenced from:
      myproj_ios::myproj_offline::ffi::get_api_url::h8098cba097ff8c42 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$get_authentication_token", referenced from:
      myproj_ios::api::authorization_header::h3cb62deba6add7c5 in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$get_web_url", referenced from:
      myproj_ios::myproj_offline::ffi::get_web_url::h428b19a7e304204c in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
  "___swift_bridge__$log_to_swift", referenced from:
      myproj_ios::logging::ffi_log_to_swift::h7bb085a9a47d98bc in libmyproj_ios.a(myproj_ios-d575b7966d7d1c3d.myproj_ios.45588441-cgu.0.rcgu.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Basically it can't link against anything that is declared extern "Swift". I've tried a few things before reaching out, but I can't figure out what is going wrong.

Please help 🙏

@chinedufn
Copy link
Owner

How are you building your iOS application?

If you're using the Xcode + Cargo approach, if you look for LIBRARY_SEARCH_PATHS in your project.pbxproj do you see the path to your release binary?

If you're using the Swift package approach, did you include your release binary in the universal library?


But yeah, please go into more detail about your build setup.

@bes
Copy link
Contributor Author

bes commented Feb 11, 2023

I am using the Swift package approach.

The binary is built like this:

    cargo build --release --target x86_64-apple-darwin
    cargo build --release --target aarch64-apple-darwin
    lipo \
        ./target/aarch64-apple-darwin/release/libmyproj_ios.a \
        ./target/x86_64-apple-darwin/release/libmyproj_ios.a -create -output \
        ./target/universal-macos/release/libmyproj_ios.a

    cargo build --release --target aarch64-apple-ios
    cargo build --release --target x86_64-apple-ios
    cargo build --release --target aarch64-apple-ios-sim
    lipo \
        ./target/aarch64-apple-ios-sim/release/libmyproj_ios.a \
        ./target/x86_64-apple-ios/release/libmyproj_ios.a -create -output \
        ./target/universal-ios/release/libmyproj_ios.a
    swift-bridge-cli create-package \
        --bridges-dir ./generated \
        --out-dir ../../video-ios/lib-myproj-ios \
        --ios target/aarch64-apple-ios/release/libmyproj_ios.a \
        --simulator target/universal-ios/release/libmyproj_ios.a \
        --macos target/universal-macos/release/libmyproj_ios.a \
        --name myprojIos

I didn't originally include the macos target, but added that as a way to see if I could move forward.

I'm happy to share more about my setup, but I'm not sure what to look for. Any help would be appreciated.

@chinedufn
Copy link
Owner

Gotcha.

Those symbols should be in the Swift code that swift-bridge is generating for you.

So, it sounds like your generated Swift code is being included in your Swift package in Debug, but not in Release.

Can you compare the Swift and C code in that ./generated directory between Debug and Release to see if the symbols are present in debug builds but absent in release builds?


If you'd like to explore yourself you can do clone the swift-bridge repo and use cargo run -p swift-bridge-cli create-package ... then poke around in here https://github.com/chinedufn/swift-bridge/blob/master/crates/swift-bridge-build/src/package.rs#L97-L112


If you can make a minimal reproduction of the issue such that we can run something like:

git clone https://github.com/bes/swift-bridge-issue-166
xcodebuild # whatever arguments are necessary ...

Then we can look into this.

@bes
Copy link
Contributor Author

bes commented Feb 12, 2023

The thing is - I'm using the SAME swift-bridge package for XCiode Debug and XCode Release builds! What I mean is that my XCode project Debug build, the one that I use for debugging locally, is working, but when I switch over to Release to package it for App Store, then it fails - with no changes in the SPM pacakage.

I am also not using a spm-over-git setup, but rather I have embedded the package as a "local" spm package in XCode.

But I compared the output from the xcode compiler, and the code in the generated .swift file:

//       ___swift_bridge__$OfflineReceiver$_free
@_cdecl("__swift_bridge__$OfflineReceiver$_free")

I build my xcode project using Fastlane, which looks like this:

  lane :build_debug do
    gym(
      scheme: 'X-debug',
      configuration: 'Debug',
      include_symbols: true,
      export_options: {
        compileBitcode: false,
        uploadBitcode: false,
        uploadSymbols: true,
        method: "development",
        teamID: "xxx",
        provisioningProfiles: {
          // ...
        },
      },
    )
  end

  lane :build_release do
    gym(
      scheme: "X-release",
      configuration: "Release",
      export_method: "app-store",
      export_options: {
        compileBitcode: false,
        uploadBitcode: false,
        uploadSymbols: true,
        method: "app-store",
        teamID: "xxx",
        provisioningProfiles: {
            // ...
        },
      },
    )
  end

But the build failure also happens when I switch over to the release profile in xcode and build from there.

I will try to make a proof-of-concept, but that will probably take some time to get working.

Summary:

  • Local SPM package in XCode.
  • It's the XCode Release build that fails, but XCode Debug build works.
  • Doing an Archive in XCode or doing a release build in fastlane fails.
  • Debug build in XCode works, with the same SPM package.

@bes
Copy link
Contributor Author

bes commented Feb 12, 2023

OK so I made some progress -

@_cdecl("__swift_bridge__$OfflineReceiver$_free")
public func __swift_bridge__OfflineReceiver__free (ptr: UnsafeMutableRawPointer) {

If I slap public on the header functions generated by extern "Swift" then it compiles!

Seems like XCode is aggressively pruning those functions in the Release build, for some reason.

@bes
Copy link
Contributor Author

bes commented Feb 13, 2023

For now I'm making do with

sed -i '' 's/^func __swift_bridge__/public func __swift_bridge__/g' mylib.swift

But I feel like either I should modify my XCode build somehow or this is a bug in swift-bridge.

@chinedufn
Copy link
Owner

chinedufn commented Feb 13, 2023

Yeah we should solve this in swift-bridge.

We just need to make this a public func instead of func

let generated_func = format!(
r#"@_cdecl("{link_name}")
func {prefixed_fn_name} ({params}){ret} {{


We can add a mod visibility_codegen_tests to the codegen tests

mod already_declared_attribute_codegen_tests;
mod argument_label_codegen_tests;
mod async_function_codegen_tests;
mod boxed_fnonce_codegen_tests;
mod conditional_compilation_codegen_tests;
mod extern_rust_function_opaque_rust_type_argument_codegen_tests;
mod extern_rust_function_opaque_rust_type_return_codegen_tests;
mod extern_rust_method_swift_class_placement_codegen_tests;
mod extern_swift_function_opaque_swift_type_return_codegen_tests;
mod function_attribute_codegen_tests;
mod generic_opaque_rust_type_codegen_tests;
mod opaque_rust_type_codegen_tests;
mod opaque_swift_type_codegen_tests;
mod option_codegen_tests;
mod result_codegen_tests;
mod return_into_attribute_codegen_tests;
mod string_codegen_tests;
mod transparent_enum_codegen_tests;
mod transparent_struct_codegen_tests;
mod vec_codegen_tests;

And then in that module we can add a extern_swift_functions_public test where we confirm that we generate a public func @_cdecl function.


We can use ExpectedRustTokens::SkipTest and ExpectedCHeader::SkipTest, like this

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::SkipTest
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::SkipTest
}

@jmp-0x7C0
Copy link

@chinedufn I've put up a PR (#262) based on these instructions.

Yeah we should solve this in swift-bridge.

We just need to make this a public func instead of func

let generated_func = format!(
r#"@_cdecl("{link_name}")
func {prefixed_fn_name} ({params}){ret} {{

We can add a mod visibility_codegen_tests to the codegen tests

mod already_declared_attribute_codegen_tests;
mod argument_label_codegen_tests;
mod async_function_codegen_tests;
mod boxed_fnonce_codegen_tests;
mod conditional_compilation_codegen_tests;
mod extern_rust_function_opaque_rust_type_argument_codegen_tests;
mod extern_rust_function_opaque_rust_type_return_codegen_tests;
mod extern_rust_method_swift_class_placement_codegen_tests;
mod extern_swift_function_opaque_swift_type_return_codegen_tests;
mod function_attribute_codegen_tests;
mod generic_opaque_rust_type_codegen_tests;
mod opaque_rust_type_codegen_tests;
mod opaque_swift_type_codegen_tests;
mod option_codegen_tests;
mod result_codegen_tests;
mod return_into_attribute_codegen_tests;
mod string_codegen_tests;
mod transparent_enum_codegen_tests;
mod transparent_struct_codegen_tests;
mod vec_codegen_tests;

And then in that module we can add a extern_swift_functions_public test where we confirm that we generate a public func @_cdecl function.

We can use ExpectedRustTokens::SkipTest and ExpectedCHeader::SkipTest, like this

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::SkipTest
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::SkipTest
}

@extrawurst
Copy link

lol i ran into the same issue: rustunit/bevy_ios_iap@ba61693
seems xcode linker optimized funcs away that are not public and not used from inside their own swift package

@llbartekll
Copy link

hey, Im running into the same issue
@chinedufn do you think we can make it work without running the script that makes functions public?

@chinedufn
Copy link
Owner

If someone writes a PR that solves this issue, I can merge it.

A contributor wrote a pull-request that fixed it, but they are no longer planning to work on addressing the PR's feedback.

#262

Anyone can feel free to fork that branch, or create their own new brach, and then get the fix working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

5 participants