car-inference 0.52.1

Local model inference for CAR — Candle backend with Qwen3 models
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
// Compiles the FoundationModels Swift shim into a static library and
// emits the link directives that pull it (plus the framework itself
// and the Swift runtime) into car-inference.
//
// Runs on Apple Silicon macOS AND on the two iOS targets that ship in
// the XCFramework — `aarch64-apple-ios` (device) and
// `aarch64-apple-ios-sim` (simulator on Apple Silicon Macs). On every
// other host we emit nothing — `backend::foundation_models` is
// cfg-gated to the same set of targets so its `extern "C"`
// declarations never get linked.
//
// The framework is weak-linked: the produced binary still loads on
// pre-26 macOS / pre-26 iOS, it just can't invoke FM symbols (the
// runtime check in `car_fm_is_available` returns 0 there).

use std::env;
use std::path::PathBuf;
use std::process::Command;

/// Soft-warn by default; hard-error when CAR_REQUIRE_APPLE_SHIMS=1
/// is set. See car-vision/build.rs for rationale.
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."
        );
    }
}

/// Per-target shim build configuration. Encapsulates the moving
/// pieces that differ between macOS, iOS device, and iOS simulator
/// so the rest of `main` stays uniform.
struct AppleTarget {
    /// Argument to `xcrun --sdk` (e.g. `macosx`, `iphoneos`).
    sdk: &'static str,
    /// Env var holding the deployment-target version (e.g.
    /// `MACOSX_DEPLOYMENT_TARGET`).
    deployment_env: &'static str,
    /// Default deployment-target version when the env var is unset.
    default_version: &'static str,
    /// Triple prefix consumed by `swiftc -target` — version + suffix
    /// are appended at use site.
    target_prefix: &'static str,
    /// Triple suffix appended after the version (empty for device
    /// builds, `-simulator` for the iOS-sim slice).
    target_suffix: &'static str,
    /// Platform directory under the Xcode toolchain Swift runtime
    /// root, e.g. `iphoneos` or `iphonesimulator`.
    swift_platform: &'static str,
    /// Linker deployment-minimum flag consumed by Apple's clang
    /// driver.
    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",
        }),
        // Apple Silicon iOS simulator slice. Intel-Mac iOS sim
        // (`x86_64-apple-ios`) isn't in the XCFramework matrix so
        // it's intentionally absent here.
        "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");

    // Declare `car_fm_swift_built` to cargo's check-cfg machinery so
    // newer toolchains don't warn that it's unrecognized. Set later
    // only when swiftc succeeds; backend module gates its extern "C"
    // surface on it (with stubs otherwise).
    println!("cargo:rustc-check-cfg=cfg(car_fm_swift_built)");

    let Some(apple) = apple else {
        return;
    };

    // Xcode-version landmines we know about. Surface them as
    // cargo:warning so a developer on a known-bad combo sees the
    // pointer to the workaround during build instead of debugging
    // mysterious link / Metal-kernel failures.
    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() {
        // Source missing — nothing to compile, leave the backend
        // module to fail at link time with a clear error if anyone
        // tries to call it.
        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");

    // Read the deployment target from the standard toolchain env var
    // for this Apple target (`MACOSX_DEPLOYMENT_TARGET` /
    // `IPHONEOS_DEPLOYMENT_TARGET`), falling back to a sensible floor.
    // Hardcoding the version would silently disagree if the workspace
    // ever bumps the floor.
    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
    );

    // Compile Swift -> object file.
    //
    // -parse-as-library: source has no top-level statements.
    // -O: release optimisation; matches Cargo's release profile.
    // -target: deployment floor for the active Apple target. Swift
    //    `@available` annotations gate FM symbols to {macOS,iOS} 26+
    //    at runtime regardless of how low the floor is.
    // -runtime-compatibility-version none: tell swiftc not to emit
    //    `__swift_FORCE_LOAD_$_swiftCompatibility{50,51,56,Concurrency}`
    //    references. Those reference back-deployment shim static libs
    //    (`libswiftCompatibility56.a`, etc.) that aren't in the
    //    iPhoneOS SDK's `usr/lib/swift` and aren't on the rustc link
    //    line — the linker fails with `Undefined symbols:
    //    __swift_FORCE_LOAD_$_swiftCompatibility56`. The shim's
    //    Concurrency usage works fine without the back-compat library
    //    on iOS 15+ / macOS 12+ (native Concurrency runtime), and the
    //    framework's `@available(iOS 26)` gate already prevents
    //    invocation on older OSes.
    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;
    }

    // Pack the object into a static library so cargo's linker can pick
    // it up via -l.
    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");

    // Weak-link the framework so the binary still loads on pre-26
    // OSes. The runtime availability check inside the Swift shim is
    // what actually gates calls.
    println!(
        "cargo:rustc-link-arg={}={}",
        apple.min_version_flag, deployment_target
    );

    // ...but only when the active SDK actually ships the framework.
    // `-weak_framework` controls how the *load command* is emitted at
    // runtime; it does NOT make the framework optional at link time —
    // `ld` still resolves the name against the SDK and hard-errors
    // with `framework 'FoundationModels' not found` when it is absent.
    // Pre-26 SDKs (Xcode <= 16.x) do not ship it.
    //
    // That is exactly the case the Swift side already handles: every
    // FM code path in the shim sits behind `#if canImport(
    // FoundationModels)`, so on such an SDK the compiled object
    // references no FM symbols at all and the framework is not needed.
    // These two guards must agree — emitting the link arg while the
    // shim compiled its stub path is what broke `check-macos-server`
    // on the macos-15 runner (Xcode 16.2 / MacOSX15.2.sdk).
    //
    // Every `@_cdecl` entry point is declared outside those blocks, so
    // the static library still exports the full C ABI and
    // `car_fm_swift_built` stays correct — `car_fm_is_available`
    // simply reports 0.
    if sdk_has_framework(apple.sdk, "FoundationModels") {
        println!("cargo:rustc-link-arg=-Wl,-weak_framework,FoundationModels");
    } else {
        println!(
            "cargo:warning=FoundationModels is not in the {} SDK (Xcode 26+ required); \
             linking without it. The `apple/foundation:default` model route will report \
             unavailable at runtime and the adaptive router will fall through to other \
             backends.",
            apple.sdk
        );
    }

    // Swift standard library + concurrency runtime. The shim uses
    // `Task` / `async`, so libswift_Concurrency must be linked too.
    // `-rpath` lets the binary find the matching dylibs in the OS's
    // built-in Swift runtime location (present on macOS 10.14+ and
    // iOS 12.2+).
    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");

    // Tell our backend module that the library is available so it can
    // unlock the extern "C" declarations.
    println!("cargo:rustc-cfg=car_fm_swift_built");
}

/// True when the active SDK for `sdk` ships `<name>.framework`.
///
/// Used to decide whether a framework can be named on the link line at
/// all. Mirrors the `#if canImport(<name>)` guard the Swift shim uses,
/// which is the compile-side half of the same question.
///
/// Any probe failure answers `false`: a missing or unusable `xcrun`
/// means we cannot show the framework exists, and omitting the link
/// arg is the outcome that still builds.
fn sdk_has_framework(sdk: &str, name: &str) -> bool {
    let Ok(out) = Command::new("xcrun")
        .arg("--sdk")
        .arg(sdk)
        .arg("--show-sdk-path")
        .output()
    else {
        return false;
    };
    if !out.status.success() {
        return false;
    }
    let sdk_path = PathBuf::from(String::from_utf8_lossy(&out.stdout).trim().to_string());
    sdk_path
        .join("System/Library/Frameworks")
        .join(format!("{name}.framework"))
        .exists()
}

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();
    // Toolchain Swift libs live next to the SDK in the developer dir.
    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())
}

/// Warn about known Xcode/Apple Clang version combos that break
/// car-inference's transitive dep graph. Cheap probe via
/// `xcodebuild -version`. Failures (xcodebuild missing, parse
/// error, etc.) are silent — this is best-effort hint surfacing,
/// not a hard gate.
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);
    // First line: `Xcode 16.4` or `Xcode 17.0` etc.
    let Some(line) = stdout.lines().next() else {
        return;
    };
    let Some(version_str) = line.split_whitespace().nth(1) else {
        return;
    };
    // Parse "16.4" → (16, 4); "17" → (17, 0).
    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)
    };

    // Known landmines as of 2026-07. Update this list whenever a
    // new Xcode release breaks the build in a way that has a
    // documented workaround — and BOUND each entry to the versions
    // actually affected. An open-ended `>=` outlives the bug it
    // describes and then cries wolf on every build, which is how a
    // developer learns to ignore cargo warnings.
    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."
        );
    }
    // Xcode 26.4 ONLY. MetalToolchain v17.5.188.0 rejected MLX's file-scope
    // `MTL_CONST constexpr`; v17.6.42.0 (Xcode 26.5) relaxed the diagnostic and
    // the mlx-sys Metal-kernel build compiles clean again — re-confirmed
    // 2026-06-22, see the RESOLVED header of the doc below. This was `>= 4`,
    // so it kept firing on 26.5 and 26.6 and pointed developers at a document
    // whose first line says the problem is fixed.
    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. Xcode 26.5+ resolves it. See \
             docs/mlx-sys-xcode-26-4-metal-incompat.md."
        );
    }
}