makepad/libs/ai/models/speech/build.rs
Admin 7f59912916 libs/ai: one AI stack, replacing libs/ggml, llama, mlx, cuda, tts, voice2 and pbr_paint
The model code was spread across eight crates that had grown into each other:
ggml and cuda and mlx each owned part of a tensor runtime, llama and tts and
voice2 each owned part of a model, and libs/diffusion owned everything else.
They are now one tree with an explicit shape:

  libs/ai/cuda     — kernels and launch surface
  libs/ai/metal    — Metal shaders and the shim
  libs/ai/llm      — the language-model runtime (sessions, lanes, contexts,
                     the CUDA and Metal executors, the compiled Metal path)
  libs/ai/models/  — common, flux, h3, music, paint, speech, stems, vision

libs/diffusion is not deleted but demoted: what remains is the VALIDATOR
crate — several dozen `*_validate.rs` oracles that check a native
implementation against a reference, which is where they belong now that the
implementations live next door.

The functional work inside the move is mostly in the LLM runtime: N lanes that
draft while one verify batch serves all of them, per-slot prefill over a shared
folded attention arena, speculation that survives batching, and a scheduler
that reports rather than publishes. And in the CUDA build: a machine without
usable CUDA must still LINK (and say so), the default kernel arch is the
building machine's GPU, `NO_CUDA` forces the stub even where the toolkit
exists, and kernels compile in parallel with progress.

libs/video_flow is new here: classical optical flow estimation and the `mkfl`
motion-field payload — a flow field measured from a clip without a model,
which is what drives free-rate bounce-looping playback and the uprez/tween
enhance pipe.
2026-08-23 01:34:35 +02:00

122 lines
4.3 KiB
Rust

use std::env;
use std::fs;
use std::process::Command;
const IOS_DEPLOYMENT_TARGET_DEFAULT: &str = "26.0";
fn main() {
println!("cargo:rerun-if-changed=swift/tts_bridge.swift");
println!("cargo:rustc-check-cfg=cfg(no_apple_tts)");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let is_apple_host = env::var("HOST").unwrap_or_default().contains("apple");
let is_apple_target = target_os == "macos" || target_os == "ios";
// No Swift toolchain, or not an Apple target: the crate degrades to a no-op
// rather than failing the build.
if !(is_apple_host && is_apple_target) || !build_tts_bridge(&target_os) {
println!("cargo:rustc-cfg=no_apple_tts");
}
}
/// Compile `swift/tts_bridge.swift` into a static library and link it.
///
/// Unlike the speech (STT) bridge, nothing here is `async`, so Swift Concurrency
/// is never linked and the `@rpath/libswift_Concurrency.dylib` install-name
/// workaround that `makepad-voice` needs does not apply.
fn build_tts_bridge(target_os: &str) -> bool {
let out_dir = env::var("OUT_DIR").unwrap();
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let swift_src = format!("{manifest_dir}/swift/tts_bridge.swift");
let module_cache = format!("{out_dir}/swift_module_cache");
let _ = fs::create_dir_all(&module_cache);
let mut args = vec![
"-emit-library".to_string(),
"-static".to_string(),
"-parse-as-library".to_string(),
"-module-name".to_string(),
"tts_bridge".to_string(),
"-module-cache-path".to_string(),
module_cache,
"-O".to_string(),
];
if target_os == "ios" {
if let Some((target, sdk)) = ios_target_and_sdk() {
args.push("-target".to_string());
args.push(target);
args.push("-sdk".to_string());
args.push(sdk);
}
}
args.push("-o".to_string());
args.push(format!("{out_dir}/libtts_bridge.a"));
args.push(swift_src);
match Command::new("swiftc").args(&args).status() {
Ok(status) if status.success() => {}
Ok(_) => {
println!("cargo:warning=swiftc failed for tts bridge; speech is disabled");
return false;
}
Err(err) => {
println!("cargo:warning=swiftc unavailable ({err}); speech is disabled");
return false;
}
}
println!("cargo:rustc-link-search=native={out_dir}");
println!("cargo:rustc-link-lib=static=tts_bridge");
println!("cargo:rustc-link-lib=framework=Foundation");
println!("cargo:rustc-link-lib=framework=AVFoundation");
// Let the linker resolve the Swift runtime symbols the bridge pulls in.
if let Ok(output) = Command::new("swiftc").args(["-print-target-info"]).output() {
if output.status.success() {
let info = String::from_utf8_lossy(&output.stdout);
for line in info.lines() {
let path = line.trim().trim_matches('"').trim_end_matches(',');
if path.starts_with('/') && path.contains("lib/swift") {
println!("cargo:rustc-link-search=native={path}");
}
}
}
}
true
}
fn ios_target_and_sdk() -> Option<(String, String)> {
let arch = env::var("CARGO_CFG_TARGET_ARCH").ok()?;
let abi = env::var("CARGO_CFG_TARGET_ABI").unwrap_or_default();
let is_simulator = abi == "sim" || arch == "x86_64";
let swift_arch = match arch.as_str() {
"aarch64" => "arm64",
"x86_64" => "x86_64",
_ => return None,
};
let deployment_key = if is_simulator {
"IPHONESIMULATOR_DEPLOYMENT_TARGET"
} else {
"IPHONEOS_DEPLOYMENT_TARGET"
};
let deployment =
env::var(deployment_key).unwrap_or_else(|_| IOS_DEPLOYMENT_TARGET_DEFAULT.to_string());
let swift_target = if is_simulator {
format!("{swift_arch}-apple-ios{deployment}-simulator")
} else {
format!("{swift_arch}-apple-ios{deployment}")
};
let sdk_name = if is_simulator {
"iphonesimulator"
} else {
"iphoneos"
};
let sdk_path = Command::new("xcrun")
.args(["--sdk", sdk_name, "--show-sdk-path"])
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_string())?;
Some((swift_target, sdk_path))
}