From 8346bc2db255364d6b99e31a9a0ac5cc05bb29b9 Mon Sep 17 00:00:00 2001 From: Evgeny Khudoba Date: Mon, 27 Apr 2026 12:27:18 +0700 Subject: [PATCH] fix(nix): Fix for macOS build for nixpkgs (#1316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(macos): pass -parse-as-library to swiftc Without this flag swiftc compiles a single-file input in script mode and emits a synthetic `_main` into the object file. Packaged into libapple_intelligence.a and linked alongside Rust's `_main`, Apple's open-source ld64 (used by nixpkgs' Darwin stdenv) picks Swift's main, leaving the app with a 5-instruction no-op that returns 0 immediately. The binary looks complete — full Rust code, Metal, Swift runtime, onnxruntime rpath — but launching it exits cleanly with code 0, no output. Production CI masks the issue because Xcode's linker happens to prefer Rust's `_main`. `-parse-as-library` keeps swiftc in library mode so no `_main` is emitted. The @_cdecl exports used by the Rust FFI are unaffected. * fix(macos): respect SDKROOT/SWIFTC env vars for non-Xcode toolchains xcrun is unavailable in non-Xcode setups (e.g. nixpkgs uses apple-sdk_* plus a standalone swift compiler). Honor SDKROOT and SWIFTC if set; fall back to xcrun otherwise so Apple-toolchain behavior is unchanged. Also invoke swiftc directly via the resolved path rather than via `xcrun swiftc`. --- src-tauri/build.rs | 59 +++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src-tauri/build.rs b/src-tauri/build.rs index db4eabe..e43baf6 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -129,16 +129,20 @@ fn build_apple_intelligence_bridge() { let object_path = out_dir.join("apple_intelligence.o"); let static_lib_path = out_dir.join("libapple_intelligence.a"); - let sdk_path = String::from_utf8( - Command::new("xcrun") - .args(["--sdk", "macosx", "--show-sdk-path"]) - .output() - .expect("Failed to locate macOS SDK") - .stdout, - ) - .expect("SDK path is not valid UTF-8") - .trim() - .to_string(); + // SDKROOT/SWIFTC env-var overrides let non-Xcode toolchains (e.g. nixpkgs + // with apple-sdk_* + standalone swift) bypass xcrun, which is Xcode-only. + let sdk_path = env::var("SDKROOT").unwrap_or_else(|_| { + String::from_utf8( + Command::new("xcrun") + .args(["--sdk", "macosx", "--show-sdk-path"]) + .output() + .expect("Failed to locate macOS SDK") + .stdout, + ) + .expect("SDK path is not valid UTF-8") + .trim() + .to_string() + }); // Check if the SDK supports FoundationModels (required for Apple Intelligence) let framework_path = @@ -157,16 +161,19 @@ fn build_apple_intelligence_bridge() { panic!("Source file {} is missing!", source_file); } - let swiftc_path = String::from_utf8( - Command::new("xcrun") - .args(["--find", "swiftc"]) - .output() - .expect("Failed to locate swiftc") - .stdout, - ) - .expect("swiftc path is not valid UTF-8") - .trim() - .to_string(); + // See SDKROOT note above — same env-override pattern for non-Xcode toolchains. + let swiftc_path = env::var("SWIFTC").unwrap_or_else(|_| { + String::from_utf8( + Command::new("xcrun") + .args(["--find", "swiftc"]) + .output() + .expect("Failed to locate swiftc") + .stdout, + ) + .expect("swiftc path is not valid UTF-8") + .trim() + .to_string() + }); let toolchain_swift_lib = Path::new(&swiftc_path) .parent() @@ -178,9 +185,17 @@ fn build_apple_intelligence_bridge() { // Use macOS 11.0 as deployment target for compatibility // The @available(macOS 26.0, *) checks in Swift handle runtime availability // Weak linking for FoundationModels is handled via cargo:rustc-link-arg below - let status = Command::new("xcrun") + let status = Command::new(&swiftc_path) .args([ - "swiftc", + // Without this flag swiftc treats single-file input as script + // mode and emits its own `_main` symbol into the .o, which can + // win the link against Rust's main under some linkers (e.g. + // open-source ld64 used in nixpkgs' Darwin stdenv), producing a + // binary whose main() is a 5-instruction no-op that returns 0. + // `-parse-as-library` keeps the compilation in library mode so + // no `_main` is emitted. See: + // https://forums.swift.org/t/main-in-a-single-swift-file/63079 + "-parse-as-library", "-target", "arm64-apple-macosx11.0", "-sdk",