tauri-plugin-bare-kit 0.3.1

Effortless JavaScript backends for Tauri apps - Android 🤖 iOS 📱Linux 🐧 Macos 🖥️ Windows 💩
use std::{
    env, fs,
    path::{Path, PathBuf},
    process::Command,
};

#[cfg(unix)]
const NPX: &str = "npx";
#[cfg(windows)]
const NPX: &str = "npx.cmd";

pub fn autolink() {
    let src = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let project = src.parent().unwrap();

    let entry = Some(project.join("bare/app.js"));
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        Some(project.join("bare/dist/app.js"))
    };
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        Some(project.join("bare/src/app.js"))
    };
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        Some(project.join("src-bare/app.js"))
    };
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        Some(project.join("src-bare/dist/app.js"))
    };
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        Some(project.join("src-bare/src/app.js"))
    };
    let entry = if entry.as_ref().unwrap().exists() {
        entry
    } else {
        None
    };

    let resource_dir = env::var("DEP_TAURI_PLUGIN_BARE_KIT_RESOURCE_DIR").unwrap();

    let platform = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let platform = match &*platform {
        "macos" => "darwin",
        "windows" => "win32",
        platform => platform,
    };
    let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    let arch = match &*arch {
        "arm" => "arm",
        "aarch64" => "arm64",
        "i686" => "ia32",
        "x86_64" => "x64",
        arch => panic!("Unsupported architecture: {arch}"),
    };

    if vec!["darwin", "linux", "win32"].contains(&platform) {
        env::set_var(
            "TAURI_CONFIG",
            format!(
                "{{ \"bundle\": {{ \"resources\": {{ \"{}\": \"\" }} }} }}",
                resource_dir.replace("\\", "\\\\"),
            ),
        );
    }

    if platform == "darwin" {
        let host = format!("{platform}-{arch}");

        assert!(Command::new(NPX)
            .current_dir(project)
            .args(vec![
                "--yes",
                "bare-link",
                "--host",
                &*host,
                "--out",
                &*resource_dir,
                project.to_str().unwrap(),
            ])
            .status()
            .unwrap()
            .success());

        if let Some(entry) = &entry {
            assert!(Command::new(NPX)
                .current_dir(project)
                .args(vec![
                    "--yes",
                    "bare-pack",
                    "--host",
                    &*host,
                    "--linked",
                    "--out",
                    project.join("app.bundle.json").to_str().unwrap(),
                    entry.to_str().unwrap(),
                ])
                .status()
                .unwrap()
                .success());
        }

        println!("cargo::rustc-link-arg=-Wl,-rpath,@executable_path/");
        println!("cargo::rustc-link-search=framework={resource_dir}");

        for framework in fs::read_dir(&resource_dir).unwrap().filter_map(|e| {
            if let Ok(e) = e {
                return if e.metadata().unwrap().is_dir() {
                    Some(e.file_name().to_string_lossy().to_string())
                } else {
                    None
                };
            }
            None
        }) {
            println!(
                "cargo::rustc-link-lib=framework={}",
                framework.strip_suffix(".framework").unwrap()
            );
        }
    }

    if platform == "ios" {
        let simulator = env::var("CARGO_CFG_TARGET_ENV").unwrap() == "sim";
        let host = format!(
            "{platform}-{arch}{}",
            if simulator { "-simulator" } else { "" }
        );
        let arch = match arch {
            "arm64" => "arm64",
            "x64" => "x86_64",
            arch => panic!("Unsupported architecture: {arch}"),
        };
        let profile = env::var("PROFILE").unwrap();
        let out = src.join(format!("gen/apple/Externals/{arch}/{profile}"));

        assert!(Command::new(NPX)
            .current_dir(project)
            .args(vec![
                "--yes",
                "bare-link",
                "--host",
                &*host,
                "--out",
                out.to_str().unwrap(),
                project.to_str().unwrap(),
            ])
            .status()
            .unwrap()
            .success());

        if let Some(entry) = &entry {
            assert!(Command::new(NPX)
                .current_dir(project)
                .args(vec![
                    "--yes",
                    "bare-pack",
                    "--host",
                    &*host,
                    "--linked",
                    "--out",
                    project.join("app.bundle.json").to_str().unwrap(),
                    entry.to_str().unwrap(),
                ])
                .status()
                .unwrap()
                .success());
        }

        copy_dir_all(
            PathBuf::from(&*resource_dir).join("BareKit.framework"),
            out.join("BareKit.framework"),
        );

        println!("cargo::rustc-link-search=framework={}", out.display());
        println!("cargo::rustc-link-lib=framework=BareKit");
    }

    if platform == "android" {
        let host = format!("{platform}-{arch}");
        let arch = match &*arch {
            "arm" => "armeabi-v7a",
            "arm64" => "arm64-v8a",
            "ia32" => "x86",
            "x64" => "x86_64",
            arch => panic!("Unsupported architecture: {arch}"),
        };
        let out = src.join("gen/android/app/src/main/jniLibs");

        assert!(Command::new(NPX)
            .current_dir(project)
            .args(vec![
                "--yes",
                "bare-link",
                "--host",
                &*host,
                "--out",
                out.to_str().unwrap(),
                project.to_str().unwrap(),
            ])
            .status()
            .unwrap()
            .success());

        if let Some(entry) = &entry {
            assert!(Command::new(NPX)
                .current_dir(project)
                .args(vec![
                    "--yes",
                    "bare-pack",
                    "--host",
                    &*host,
                    "--linked",
                    "--out",
                    project.join("app.bundle.json").to_str().unwrap(),
                    entry.to_str().unwrap(),
                ])
                .status()
                .unwrap()
                .success());
        }

        let out = out.join(arch);

        fs::copy(
            PathBuf::from(&*resource_dir).join("libbare-kit.so"),
            out.join("libbare-kit.so"),
        )
        .unwrap();
        fs::copy(
            PathBuf::from(&*resource_dir).join("libc++_shared.so"),
            out.join("libc++_shared.so"),
        )
        .unwrap();

        println!("cargo::rustc-link-search=native={}", out.display());
        println!("cargo::rustc-link-lib=bare-kit");
    }

    if platform == "linux" {
        let out = PathBuf::from(env::var("OUT_DIR").unwrap());
        let host = format!("{platform}-{arch}");

        assert!(Command::new(NPX)
            .current_dir(project)
            .args(vec![
                "--yes",
                "bare-link",
                "--host",
                &*host,
                "--out",
                out.to_str().unwrap(),
                project.to_str().unwrap(),
            ])
            .status()
            .unwrap()
            .success());

        if let Some(entry) = &entry {
            assert!(Command::new(NPX)
                .current_dir(project)
                .args(vec![
                    "--yes",
                    "bare-pack",
                    "--host",
                    &*host,
                    "--linked",
                    "--out",
                    project.join("app.bundle.json").to_str().unwrap(),
                    entry.to_str().unwrap(),
                ])
                .status()
                .unwrap()
                .success());
        }

        copy_dir_all(out.join("lib"), PathBuf::from(&*resource_dir));

        println!("cargo::rustc-link-arg=-Wl,-rpath=$ORIGIN");
        println!("cargo::rustc-link-search=native={resource_dir}");

        for lib in fs::read_dir(&resource_dir).unwrap().filter_map(|e| {
            if let Ok(e) = e {
                let fname = e.file_name().to_string_lossy().to_string();

                return if fname.starts_with("lib") && fname.ends_with(".so") {
                    Some(fname)
                } else {
                    None
                };
            }
            None
        }) {
            let lname = lib
                .strip_prefix("lib")
                .unwrap()
                .strip_suffix(".so")
                .unwrap();

            println!("cargo::rustc-link-lib={lname}");
        }
    }

    if platform == "win32" {
        let host = format!("{platform}-{arch}");

        assert!(Command::new(NPX)
            .current_dir(project)
            .args(vec![
                "--yes",
                "bare-link",
                "--host",
                &*host,
                "--out",
                &*resource_dir,
                project.to_str().unwrap(),
            ])
            .status()
            .unwrap()
            .success());

        if let Some(entry) = &entry {
            assert!(Command::new(NPX)
                .current_dir(project)
                .args(vec![
                    "--yes",
                    "bare-pack",
                    "--host",
                    &*host,
                    "--linked",
                    "--out",
                    project.join("app.bundle.json").to_str().unwrap(),
                    entry.to_str().unwrap(),
                ])
                .status()
                .unwrap()
                .success());
        }

        println!(
            "cargo:rustc-link-arg=/DEF:{}",
            PathBuf::from(&*resource_dir).join("bare-kit.def").display(),
        );
    }
}

fn copy_dir_all<P: AsRef<Path>>(src: P, dest: P) {
    fs::create_dir_all(&dest).unwrap();

    for entry in fs::read_dir(src).unwrap() {
        let entry = entry.unwrap();
        let ftype = entry.file_type().unwrap();
        if ftype.is_dir() {
            copy_dir_all(entry.path(), dest.as_ref().join(entry.file_name()));
        } else {
            fs::copy(entry.path(), dest.as_ref().join(entry.file_name())).unwrap();
        }
    }
}