fn main() { #[cfg(all(target_os = "macos", target_arch = "aarch64"))] build_apple_intelligence_bridge(); generate_tray_translations(); tauri_build::build() } /// Generate tray menu translations from frontend locale files. /// /// Source of truth: src/i18n/locales/*/translation.json /// The English "tray" section defines the struct fields. fn generate_tray_translations() { use std::collections::BTreeMap; use std::fs; use std::path::Path; let out_dir = std::env::var("OUT_DIR").unwrap(); let locales_dir = Path::new("../src/i18n/locales"); println!("cargo:rerun-if-changed=../src/i18n/locales"); // Collect all locale translations let mut translations: BTreeMap = BTreeMap::new(); for entry in fs::read_dir(locales_dir).unwrap().flatten() { let path = entry.path(); if !path.is_dir() { continue; } let lang = path.file_name().unwrap().to_str().unwrap().to_string(); let json_path = path.join("translation.json"); println!("cargo:rerun-if-changed={}", json_path.display()); let content = fs::read_to_string(&json_path).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&content).unwrap(); if let Some(tray) = parsed.get("tray").cloned() { translations.insert(lang, tray); } } // English defines the schema let english = translations.get("en").unwrap().as_object().unwrap(); let fields: Vec<_> = english .keys() .map(|k| (camel_to_snake(k), k.clone())) .collect(); // Generate code let mut out = String::from( "// Auto-generated from src/i18n/locales/*/translation.json - do not edit\n\n", ); // Struct out.push_str("#[derive(Debug, Clone)]\npub struct TrayStrings {\n"); for (rust_field, _) in &fields { out.push_str(&format!(" pub {rust_field}: String,\n")); } out.push_str("}\n\n"); // Static map out.push_str( "pub static TRANSLATIONS: Lazy> = Lazy::new(|| {\n", ); out.push_str(" let mut m = HashMap::new();\n"); for (lang, tray) in &translations { out.push_str(&format!(" m.insert(\"{lang}\", TrayStrings {{\n")); for (rust_field, json_key) in &fields { let val = tray.get(json_key).and_then(|v| v.as_str()).unwrap_or(""); out.push_str(&format!( " {rust_field}: \"{}\".to_string(),\n", escape_string(val) )); } out.push_str(" });\n"); } out.push_str(" m\n});\n"); fs::write(Path::new(&out_dir).join("tray_translations.rs"), out).unwrap(); println!( "cargo:warning=Generated tray translations: {} languages, {} fields", translations.len(), fields.len() ); } fn camel_to_snake(s: &str) -> String { s.chars() .enumerate() .fold(String::new(), |mut acc, (i, c)| { if c.is_uppercase() && i > 0 { acc.push('_'); } acc.push(c.to_lowercase().next().unwrap()); acc }) } fn escape_string(s: &str) -> String { s.replace('\\', "\\\\") .replace('"', "\\\"") .replace('\n', "\\n") .replace('\r', "\\r") .replace('\t', "\\t") } #[cfg(all(target_os = "macos", target_arch = "aarch64"))] fn build_apple_intelligence_bridge() { use std::env; use std::path::{Path, PathBuf}; use std::process::Command; const REAL_SWIFT_FILE: &str = "swift/apple_intelligence.swift"; const STUB_SWIFT_FILE: &str = "swift/apple_intelligence_stub.swift"; const BRIDGE_HEADER: &str = "swift/apple_intelligence_bridge.h"; println!("cargo:rerun-if-changed={REAL_SWIFT_FILE}"); println!("cargo:rerun-if-changed={STUB_SWIFT_FILE}"); println!("cargo:rerun-if-changed={BRIDGE_HEADER}"); let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); let object_path = out_dir.join("apple_intelligence.o"); let static_lib_path = out_dir.join("libapple_intelligence.a"); // SDKROOT/SWIFTC env-var overrides let non-Xcode toolchains (e.g. nixpkgs // with apple-sdk_* + standalone swift) bypass xcrun, which is Xcode-only. let sdk_path = env::var("SDKROOT").unwrap_or_else(|_| { String::from_utf8( Command::new("xcrun") .args(["--sdk", "macosx", "--show-sdk-path"]) .output() .expect("Failed to locate macOS SDK") .stdout, ) .expect("SDK path is not valid UTF-8") .trim() .to_string() }); // Check if the SDK supports FoundationModels (required for Apple Intelligence) let framework_path = Path::new(&sdk_path).join("System/Library/Frameworks/FoundationModels.framework"); let has_foundation_models = framework_path.exists(); let source_file = if has_foundation_models { println!("cargo:warning=Building with Apple Intelligence support."); REAL_SWIFT_FILE } else { println!("cargo:warning=Apple Intelligence SDK not found. Building with stubs."); STUB_SWIFT_FILE }; if !Path::new(source_file).exists() { panic!("Source file {} is missing!", source_file); } // See SDKROOT note above — same env-override pattern for non-Xcode toolchains. let swiftc_path = env::var("SWIFTC").unwrap_or_else(|_| { String::from_utf8( Command::new("xcrun") .args(["--find", "swiftc"]) .output() .expect("Failed to locate swiftc") .stdout, ) .expect("swiftc path is not valid UTF-8") .trim() .to_string() }); let toolchain_swift_lib = Path::new(&swiftc_path) .parent() .and_then(|p| p.parent()) .map(|root| root.join("lib/swift/macosx")) .expect("Unable to determine Swift toolchain lib directory"); let sdk_swift_lib = Path::new(&sdk_path).join("usr/lib/swift"); // Use macOS 11.0 as deployment target for compatibility // The @available(macOS 26.0, *) checks in Swift handle runtime availability // Weak linking for FoundationModels is handled via cargo:rustc-link-arg below let status = Command::new(&swiftc_path) .args([ // Without this flag swiftc treats single-file input as script // mode and emits its own `_main` symbol into the .o, which can // win the link against Rust's main under some linkers (e.g. // open-source ld64 used in nixpkgs' Darwin stdenv), producing a // binary whose main() is a 5-instruction no-op that returns 0. // `-parse-as-library` keeps the compilation in library mode so // no `_main` is emitted. See: // https://forums.swift.org/t/main-in-a-single-swift-file/63079 "-parse-as-library", "-target", "arm64-apple-macosx11.0", "-sdk", &sdk_path, "-O", "-import-objc-header", BRIDGE_HEADER, "-c", source_file, "-o", object_path .to_str() .expect("Failed to convert object path to string"), ]) .status() .expect("Failed to invoke swiftc for Apple Intelligence bridge"); if !status.success() { panic!("swiftc failed to compile {source_file}"); } let status = Command::new("libtool") .args([ "-static", "-o", static_lib_path .to_str() .expect("Failed to convert static lib path to string"), object_path .to_str() .expect("Failed to convert object path to string"), ]) .status() .expect("Failed to create static library for Apple Intelligence bridge"); if !status.success() { panic!("libtool failed for Apple Intelligence bridge"); } println!("cargo:rustc-link-search=native={}", out_dir.display()); println!("cargo:rustc-link-lib=static=apple_intelligence"); println!( "cargo:rustc-link-search=native={}", toolchain_swift_lib.display() ); println!("cargo:rustc-link-search=native={}", sdk_swift_lib.display()); println!("cargo:rustc-link-lib=framework=Foundation"); if has_foundation_models { // Use weak linking so the app can launch on systems without FoundationModels println!("cargo:rustc-link-arg=-weak_framework"); println!("cargo:rustc-link-arg=FoundationModels"); } println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift"); }