fix(nix): Fix for macOS build for nixpkgs (#1316)

* 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`.
This commit is contained in:
Evgeny Khudoba 2026-04-27 12:27:18 +07:00 committed by GitHub
parent 4b7bb4e5c4
commit 8346bc2db2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -129,16 +129,20 @@ fn build_apple_intelligence_bridge() {
let object_path = out_dir.join("apple_intelligence.o"); let object_path = out_dir.join("apple_intelligence.o");
let static_lib_path = out_dir.join("libapple_intelligence.a"); let static_lib_path = out_dir.join("libapple_intelligence.a");
let sdk_path = String::from_utf8( // SDKROOT/SWIFTC env-var overrides let non-Xcode toolchains (e.g. nixpkgs
Command::new("xcrun") // with apple-sdk_* + standalone swift) bypass xcrun, which is Xcode-only.
.args(["--sdk", "macosx", "--show-sdk-path"]) let sdk_path = env::var("SDKROOT").unwrap_or_else(|_| {
.output() String::from_utf8(
.expect("Failed to locate macOS SDK") Command::new("xcrun")
.stdout, .args(["--sdk", "macosx", "--show-sdk-path"])
) .output()
.expect("SDK path is not valid UTF-8") .expect("Failed to locate macOS SDK")
.trim() .stdout,
.to_string(); )
.expect("SDK path is not valid UTF-8")
.trim()
.to_string()
});
// Check if the SDK supports FoundationModels (required for Apple Intelligence) // Check if the SDK supports FoundationModels (required for Apple Intelligence)
let framework_path = let framework_path =
@ -157,16 +161,19 @@ fn build_apple_intelligence_bridge() {
panic!("Source file {} is missing!", source_file); panic!("Source file {} is missing!", source_file);
} }
let swiftc_path = String::from_utf8( // See SDKROOT note above — same env-override pattern for non-Xcode toolchains.
Command::new("xcrun") let swiftc_path = env::var("SWIFTC").unwrap_or_else(|_| {
.args(["--find", "swiftc"]) String::from_utf8(
.output() Command::new("xcrun")
.expect("Failed to locate swiftc") .args(["--find", "swiftc"])
.stdout, .output()
) .expect("Failed to locate swiftc")
.expect("swiftc path is not valid UTF-8") .stdout,
.trim() )
.to_string(); .expect("swiftc path is not valid UTF-8")
.trim()
.to_string()
});
let toolchain_swift_lib = Path::new(&swiftc_path) let toolchain_swift_lib = Path::new(&swiftc_path)
.parent() .parent()
@ -178,9 +185,17 @@ fn build_apple_intelligence_bridge() {
// Use macOS 11.0 as deployment target for compatibility // Use macOS 11.0 as deployment target for compatibility
// The @available(macOS 26.0, *) checks in Swift handle runtime availability // The @available(macOS 26.0, *) checks in Swift handle runtime availability
// Weak linking for FoundationModels is handled via cargo:rustc-link-arg below // Weak linking for FoundationModels is handled via cargo:rustc-link-arg below
let status = Command::new("xcrun") let status = Command::new(&swiftc_path)
.args([ .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", "-target",
"arm64-apple-macosx11.0", "arm64-apple-macosx11.0",
"-sdk", "-sdk",