tauri-plugin-secure-element 0.1.0-beta.4

Tauri plugin for secure element use on iOS (Secure Enclave) and Android (Strongbox and TEE).
use std::env;
use std::fs;
use std::path::Path;

const COMMANDS: &[&str] = &["ping"];

fn main() {
    // Declare cfg flags for Tauri platform detection
    // This allows the code to compile during cargo package even when these flags aren't set
    println!("cargo::rustc-check-cfg=cfg(desktop)");
    println!("cargo::rustc-check-cfg=cfg(mobile)");
    // Check if we're running during cargo package
    // During packaging, cargo copies source files to target/package/ and runs build.rs
    // We detect this by checking if CARGO_MANIFEST_DIR or OUT_DIR contains "target/package"
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
    let out_dir = env::var("OUT_DIR").unwrap_or_default();
    let is_packaging =
        manifest_dir.contains("target/package") || out_dir.contains("target/package");

    // Check if autogenerated files already exist
    let autogenerated_commands_dir = Path::new("permissions/autogenerated/commands");
    let files_exist = autogenerated_commands_dir.exists()
        && fs::read_dir(autogenerated_commands_dir).is_ok_and(|dir| dir.count() > 0);

    // Skip generation if we're packaging and files already exist
    // This prevents modifying the source directory during cargo package
    // The permissions directory (including autogenerated files) is included in the package
    // via the include field in Cargo.toml, so they should already be present
    if is_packaging && files_exist {
        println!(
            "cargo:warning=Skipping permission generation during packaging - files already exist"
        );
        return;
    }

    tauri_plugin::Builder::new(COMMANDS)
        .android_path("android")
        .ios_path("ios")
        .build();

    // Compile Swift code for macOS ONLY
    #[cfg(target_os = "macos")]
    {
        // Double-check target explicitly to ensure we only run for macOS, not iOS
        let target = std::env::var("TARGET").unwrap_or_default();
        if target.contains("apple-ios") {
            // Explicitly skip for iOS builds
            return;
        }
        use std::path::PathBuf;
        use std::process::Command;

        let swift_core = PathBuf::from("swift/SecureEnclaveCore.swift");
        let swift_ffi = PathBuf::from("swift/secure_element_ffi.swift");

        if !swift_core.exists() || !swift_ffi.exists() {
            println!("cargo:warning=Swift files not found, skipping macOS build");
            return;
        }

        // Tell Cargo to rerun this build script if Swift files change
        println!("cargo:rerun-if-changed={}", swift_core.display());
        println!("cargo:rerun-if-changed={}", swift_ffi.display());

        let out_dir = std::env::var("OUT_DIR").unwrap();

        // Determine target architecture from Cargo's target configuration
        let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| {
            // Fallback: try to detect from TARGET triple
            if target.starts_with("x86_64") {
                "x86_64".to_string()
            } else {
                "aarch64".to_string()
            }
        });

        // Map Cargo arch names to Apple arch names
        let swift_target = match target_arch.as_str() {
            "x86_64" => "x86_64-apple-macosx10.15",
            "aarch64" | "arm64" => "arm64-apple-macosx11.0",
            other => {
                println!(
                    "cargo:warning=Unsupported architecture '{}', defaulting to arm64",
                    other
                );
                "arm64-apple-macosx11.0"
            }
        };

        // Get macOS SDK path
        let sdk_output = Command::new("xcrun")
            .args(["--show-sdk-path", "--sdk", "macosx"])
            .output();

        let sdk_path = match sdk_output {
            Ok(output) => {
                let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
                if path.is_empty() {
                    println!("cargo:warning=Failed to get macOS SDK path");
                    return;
                }
                path
            }
            Err(e) => {
                println!("cargo:warning=Failed to run xcrun: {}", e);
                return;
            }
        };

        // Compile Swift files to object file (swiftc can compile multiple files at once)
        let object_file = format!("{}/secure_element.o", out_dir);
        let swift_status = Command::new("swiftc")
            .args([
                "-c",
                swift_core.to_str().unwrap(),
                swift_ffi.to_str().unwrap(),
                "-o",
                object_file.as_str(),
                "-target",
                swift_target,
                "-sdk",
                sdk_path.as_str(),
                "-whole-module-optimization",
            ])
            .output();

        match swift_status {
            Ok(output) => {
                if !output.status.success() {
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    println!("cargo:warning=Swift compilation failed: {}", stderr);
                    return;
                }
            }
            Err(e) => {
                println!("cargo:warning=Failed to run swiftc: {}", e);
                return;
            }
        }

        // Create static library from object file
        let lib_path = format!("{}/libsecure_element.a", out_dir);
        let ar_status = Command::new("ar")
            .args(["rcs", lib_path.as_str(), object_file.as_str()])
            .output();

        if let Ok(output) = ar_status {
            if output.status.success() {
                // Double-check we're building for macOS (not iOS or Android)
                let target = std::env::var("TARGET").unwrap_or_default();
                if !target.contains("apple-darwin")
                    || target.contains("apple-ios")
                    || target.contains("android")
                {
                    // Skip linking for non-macOS targets
                    return;
                }

                // Get Swift toolchain path dynamically using xcrun
                let toolchain_lib_path = Command::new("xcrun")
                    .args(["--find", "swiftc"])
                    .output()
                    .ok()
                    .and_then(|output| {
                        if output.status.success() {
                            let swiftc_path = String::from_utf8_lossy(&output.stdout)
                                .trim()
                                .to_string();
                            // swiftc is at .../Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc
                            // We need .../Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx
                            PathBuf::from(&swiftc_path)
                                .parent() // usr/bin
                                .and_then(|p| p.parent()) // usr
                                .map(|p| p.join("lib/swift/macosx"))
                                .map(|p| p.to_string_lossy().to_string())
                        } else {
                            None
                        }
                    })
                    .unwrap_or_else(|| {
                        // Fallback to standard Xcode location
                        "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx".to_string()
                    });

                // Tell cargo to link the library (only for macOS)
                println!("cargo:rustc-link-search=native={}", out_dir);
                println!("cargo:rustc-link-search=native={}", toolchain_lib_path);
                println!("cargo:rustc-link-lib=static=secure_element");
                println!("cargo:rustc-link-lib=framework=Security");
                println!("cargo:rustc-link-lib=framework=Foundation");
                // Link Swift compatibility libraries (macOS only)
                println!("cargo:rustc-link-lib=static=swiftCompatibility56");
                println!("cargo:rustc-link-lib=static=swiftCompatibilityConcurrency");
            }
        }
    }
}