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 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",