makepad/libs/tts/build.rs
Admin 449f51be7a gamemaker prerequisites: makepad-tts library, TTS/whisper model downloader, aigame design docs
libs/tts was never tracked despite being a build dependency of the gamemaker
example. tools/download_tts.sh fetches the public upstream weights (HuggingFace
Kokoro-82M + whisper.cpp) and converts them locally with the in-repo stdlib-only
converter; model artifacts are gitignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:29:22 +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))
}