boltffi_cli 0.22.1

CLI for BoltFFI - generate Swift, Kotlin, and WASM bindings from Rust
use std::path::PathBuf;

use crate::check::EnvironmentCheck;
use crate::config::Config;
use crate::error::Result;
use crate::target::RustTarget;

pub struct DoctorOptions {
    pub apple: bool,
    pub android: bool,
    pub wasm: bool,
}

pub fn run_doctor(options: DoctorOptions) -> Result<()> {
    let required_targets = required_targets(&options);
    let check = EnvironmentCheck::run(&required_targets);

    println!("boltffi doctor");
    println!();
    print_environment(&check, &options);
    println!();
    print_config_summary();

    Ok(())
}

fn required_targets(options: &DoctorOptions) -> Vec<RustTarget> {
    let apple_targets = options
        .apple
        .then(|| RustTarget::ALL_IOS.iter().cloned())
        .into_iter()
        .flatten();

    let android_targets = options
        .android
        .then(|| RustTarget::ALL_ANDROID.iter().cloned())
        .into_iter()
        .flatten();

    let wasm_targets = options
        .wasm
        .then(|| RustTarget::ALL_WASM.iter().cloned())
        .into_iter()
        .flatten();

    apple_targets
        .chain(android_targets)
        .chain(wasm_targets)
        .collect()
}

fn print_environment(check: &EnvironmentCheck, options: &DoctorOptions) {
    match &check.rust_version {
        Some(version) => println!("Rust: {}", version),
        None => println!("Rust: missing"),
    }

    println!("Installed targets: {}", check.installed_targets.len());
    println!("Missing targets: {}", check.missing_targets.len());
    check
        .missing_targets
        .iter()
        .for_each(|triple| println!("  - {}", triple));

    println!();
    println!("Apple tooling: {}", readiness(check.is_ready_for_apple()));
    if options.apple {
        println!("  xcode-select: {}", readiness(check.tools.xcode_cli));
        println!("  xcodebuild: {}", readiness(check.tools.xcodebuild));
        println!("  lipo: {}", readiness(check.tools.lipo));
    }

    println!();
    println!(
        "Android tooling: {}",
        readiness(check.is_ready_for_android())
    );
    if options.android {
        match &check.tools.android_ndk {
            Some(path) => println!("  ndk: {}", path),
            None => println!("  ndk: missing (set ANDROID_NDK_HOME)"),
        }
    }

    if options.wasm {
        println!();
        println!(
            "WASM target {}",
            readiness(
                check
                    .installed_targets
                    .iter()
                    .any(|target| target == RustTarget::WASM32_UNKNOWN_UNKNOWN.triple())
            )
        );
    }
}

fn print_config_summary() {
    let config_path = PathBuf::from("boltffi.toml");

    if !config_path.exists() {
        println!("Config: missing (expected ./boltffi.toml)");
        return;
    }

    match Config::load(&config_path) {
        Ok(config) => {
            println!("Config: {}", config_path.display());
            println!("  crate: {}", config.library_name());
            println!(
                "  targets.apple.output: {}",
                config.apple_output().display()
            );
            println!(
                "  targets.apple.swift.output: {}",
                config.apple_swift_output().display()
            );
            println!(
                "  targets.apple.header.output: {}",
                config.apple_header_output().display()
            );
            println!(
                "  targets.apple.xcframework.output: {}",
                config.apple_xcframework_output().display()
            );
            println!(
                "  targets.apple.spm.output: {}",
                config.apple_spm_output().display()
            );
            println!(
                "  targets.android.output: {}",
                config.android_output().display()
            );
            println!(
                "  targets.android.kotlin.output: {}",
                config.android_kotlin_output().display()
            );
            println!(
                "  targets.android.header.output: {}",
                config.android_header_output().display()
            );
            println!(
                "  targets.android.pack.output: {}",
                config.android_pack_output().display()
            );
            println!("  targets.wasm.output: {}", config.wasm_output().display());
            println!(
                "  targets.wasm.typescript.output: {}",
                config.wasm_typescript_output().display()
            );
            println!(
                "  targets.wasm.npm.output: {}",
                config.wasm_npm_output().display()
            );
        }
        Err(error) => {
            println!("Config: {} (invalid: {})", config_path.display(), error);
        }
    }
}

fn readiness(is_ready: bool) -> &'static str {
    if is_ready { "ok" } else { "missing" }
}