use std::env;
use std::path::PathBuf;
use std::process::Command;
fn shim_skipped(reason: String) {
if std::env::var("CAR_REQUIRE_APPLE_SHIMS").as_deref() == Ok("1") {
println!("cargo::error={reason}");
} else {
println!("cargo:warning={reason}");
println!(
"cargo:warning=FoundationModels shim skipped — `apple/foundation:default` model \
route will be unavailable; the adaptive router will fall through to other \
backends. Set CAR_REQUIRE_APPLE_SHIMS=1 to fail the build instead."
);
}
}
struct AppleTarget {
sdk: &'static str,
deployment_env: &'static str,
default_version: &'static str,
target_prefix: &'static str,
target_suffix: &'static str,
swift_platform: &'static str,
min_version_flag: &'static str,
}
fn detect_apple_target(target: &str) -> Option<AppleTarget> {
match target {
"aarch64-apple-darwin" => Some(AppleTarget {
sdk: "macosx",
deployment_env: "MACOSX_DEPLOYMENT_TARGET",
default_version: "15.0",
target_prefix: "arm64-apple-macosx",
target_suffix: "",
swift_platform: "macosx",
min_version_flag: "-mmacosx-version-min",
}),
"aarch64-apple-ios" => Some(AppleTarget {
sdk: "iphoneos",
deployment_env: "IPHONEOS_DEPLOYMENT_TARGET",
default_version: "15.0",
target_prefix: "arm64-apple-ios",
target_suffix: "",
swift_platform: "iphoneos",
min_version_flag: "-miphoneos-version-min",
}),
"aarch64-apple-ios-sim" => Some(AppleTarget {
sdk: "iphonesimulator",
deployment_env: "IPHONEOS_DEPLOYMENT_TARGET",
default_version: "15.0",
target_prefix: "arm64-apple-ios",
target_suffix: "-simulator",
swift_platform: "iphonesimulator",
min_version_flag: "-mios-simulator-version-min",
}),
_ => None,
}
}
fn main() {
let target = env::var("TARGET").unwrap_or_default();
let apple = detect_apple_target(&target);
println!("cargo:rerun-if-changed=swift/CarFoundationModels.swift");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-check-cfg=cfg(car_fm_swift_built)");
let Some(apple) = apple else {
return;
};
if apple.sdk == "macosx" {
warn_known_xcode_landmines();
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let swift_src = manifest_dir.join("swift").join("CarFoundationModels.swift");
if !swift_src.exists() {
shim_skipped(format!(
"FoundationModels Swift shim not found at {}",
swift_src.display()
));
return;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let obj_path = out_dir.join("CarFoundationModels.o");
let lib_path = out_dir.join("libCarFoundationModels.a");
println!("cargo:rerun-if-env-changed={}", apple.deployment_env);
let deployment_target =
env::var(apple.deployment_env).unwrap_or_else(|_| apple.default_version.to_string());
let target_arg = format!(
"{}{}{}",
apple.target_prefix, deployment_target, apple.target_suffix
);
let status = Command::new("xcrun")
.arg("--sdk")
.arg(apple.sdk)
.arg("swiftc")
.arg("-parse-as-library")
.arg("-O")
.arg("-target")
.arg(&target_arg)
.arg("-runtime-compatibility-version")
.arg("none")
.arg("-emit-object")
.arg("-o")
.arg(&obj_path)
.arg(&swift_src)
.status();
let status = match status {
Ok(s) => s,
Err(e) => {
shim_skipped(format!(
"swiftc invocation failed ({e}); FoundationModels backend unavailable. \
Install full Xcode (not just Command Line Tools) to enable it."
));
return;
}
};
if !status.success() {
shim_skipped(format!(
"swiftc returned non-zero status ({:?}); FoundationModels backend unavailable",
status.code()
));
return;
}
let ar_status = Command::new("ar")
.arg("crus")
.arg(&lib_path)
.arg(&obj_path)
.status();
match ar_status {
Ok(s) if s.success() => {}
_ => {
shim_skipped(format!(
"ar failed to pack {} into {}; FoundationModels backend unavailable",
obj_path.display(),
lib_path.display()
));
return;
}
}
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=CarFoundationModels");
println!(
"cargo:rustc-link-arg={}={}",
apple.min_version_flag, deployment_target
);
println!("cargo:rustc-link-arg=-Wl,-weak_framework,FoundationModels");
println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift");
if let Ok(swift_lib) = swift_runtime_path(apple.sdk) {
println!("cargo:rustc-link-search=native={}", swift_lib);
}
if let Ok(swift_lib) = swift_toolchain_runtime_path(apple.swift_platform) {
println!("cargo:rustc-link-search=native={}", swift_lib);
}
println!("cargo:rustc-link-lib=dylib=swiftCore");
println!("cargo:rustc-link-lib=dylib=swift_Concurrency");
println!("cargo:rustc-link-lib=dylib=swiftFoundation");
println!("cargo:rustc-cfg=car_fm_swift_built");
}
fn swift_runtime_path(sdk: &str) -> Result<String, std::io::Error> {
let out = Command::new("xcrun")
.arg("--sdk")
.arg(sdk)
.arg("--show-sdk-path")
.output()?;
if !out.status.success() {
return Err(std::io::Error::other("xcrun --show-sdk-path failed"));
}
let sdk_path = String::from_utf8_lossy(&out.stdout).trim().to_string();
Ok(format!("{}/usr/lib/swift", sdk_path))
}
fn swift_toolchain_runtime_path(platform: &str) -> Result<String, std::io::Error> {
let out = Command::new("xcrun").arg("--find").arg("swiftc").output()?;
if !out.status.success() {
return Err(std::io::Error::other("xcrun --find swiftc failed"));
}
let swiftc = PathBuf::from(String::from_utf8_lossy(&out.stdout).trim().to_string());
let Some(toolchain_root) = swiftc
.parent()
.and_then(|bin| bin.parent())
.map(PathBuf::from)
else {
return Err(std::io::Error::other(
"could not resolve Swift toolchain root",
));
};
Ok(toolchain_root
.join("lib")
.join("swift")
.join(platform)
.display()
.to_string())
}
fn warn_known_xcode_landmines() {
let Ok(out) = Command::new("xcodebuild").arg("-version").output() else {
return;
};
if !out.status.success() {
return;
}
let stdout = String::from_utf8_lossy(&out.stdout);
let Some(line) = stdout.lines().next() else {
return;
};
let Some(version_str) = line.split_whitespace().nth(1) else {
return;
};
let (major, minor) = {
let mut parts = version_str.split('.');
let major: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let minor: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
(major, minor)
};
if major == 16 && minor >= 3 {
println!(
"cargo:warning=Xcode {major}.{minor} bundles Apple Clang 17, which miscompiles \
MLX's bf16 truncation. Build may fail or produce incorrect MLX inference. \
Pin to Xcode 16.2 (Clang 16) via `sudo xcode-select -s /Applications/Xcode_16.2.app`. \
See .github/workflows/ci.yml `check-macos-server` for the CI pin."
);
}
if major == 26 && minor >= 4 {
println!(
"cargo:warning=Xcode {major}.{minor} ships a Metal Toolchain version that conflicts \
with mlx-sys's compiled Metal kernels. See \
docs/mlx-sys-xcode-26-4-metal-incompat.md for the documented workaround."
);
}
}