chinedufn / swift-bridge

swift-bridge facilitates Rust and Swift interop.
https://chinedufn.github.io/swift-bridge
Apache License 2.0
841 stars 62 forks source link

Universal build for Swift package (calling from Rust, building with Tauri) #236

Closed dceddia closed 1 year ago

dceddia commented 1 year ago

Hello! Thank you for your work on this, it's been really nice to use so far. I'm building an app with Tauri and trying to get a universal build going on an M1 Mac, and hit an error during linking.

I have some wrapper scripts around this, but it's effectively running cargo tauri build --target universal-apple-darwin (I tried this on its own to make sure my scripts weren't getting in the way 😅). It failed with:

ld: in /Users/dceddia/recut/target/x86_64-apple-darwin/release/deps/libmything-64d192639b010717.rlib(Exporter.swift.o), archive member 'Exporter.swift.o' with length 209176 is not mach-o or llvm bitcode file '/Users/dceddia/recut/target/x86_64-apple-darwin/release/deps/libmything-64d192639b010717.rlib'

Narrowing it down a bit, I noticed the aarch64 build works fine (cargo tauri build --target aarch64-apple-darwin) but the x86_64 one fails with the same error (cargo tauri build --target x86_64-apple-darwin). Makes some sense I guess because I'm running on an M1 and it would be building aarch64 by default.

My Swift crate's build.rs is patterned after this example. I was hoping that Tauri's universal build would automatically call lipo, and thought I might only need to get the build.rs to respect the current TARGET and let Tauri's build handle the rest. So I modified the compile_swift function in build.rs:

fn compile_swift() {
    let swift_package_dir = manifest_dir().join("mything-swift");

    // Added this:
    // Grab the current TARGET and pull out the first segment of it ("aarch64" or "x86_64")
    let triple = std::env::var("TARGET").unwrap();
    let parts = triple.split("-").collect::<Vec<_>>();
    let arch = parts.first().clone().unwrap();

    let mut cmd = Command::new("swift");

    cmd.current_dir(swift_package_dir)
        .arg("build")
        // Added this:
        // Add the current arch to the swift command
        .args(&["--arch", &arch])
        .args(&["-Xswiftc", "-static"])
        .args(&[
            "-Xswiftc",
            "-import-objc-header",
            "-Xswiftc",
            swift_source_dir()
                .join("bridging-header.h")
                .to_str()
                .unwrap(),
        ]);

    // ...
}

This got the build to work!

I was halfway through writing this issue when I figured it out, and thought I'd just post this in case it helps anyone else.

michael-dm commented 1 year ago

Not directly related but how do you manage to get autocompletion using this "calling from Rust" workflow ? Is there a way to signal to vscode or XCode the use of bridging-header.h ?

dceddia commented 1 year ago

On the Rust side I have a ‘mod ffi’ similar to the examples in this library. That was enough to get autocompletion of my own functions in VSCode. I wouldn’t expect it could see “beyond” those into Swift anyway; it’s not exposing all of Swift, just the few specific wrappers I wrote. For both Rust and Swift I have the language server plugins installed (rust-analyzer and I forget the name of the Swift one, it was just the most popular one).