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>
122 lines
4.3 KiB
Rust
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))
|
|
}
|