boltffi_cli 0.27.5

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

use crate::build::{
    BindingExpansion, BuildOptions, BuildResult, Builder, all_successful, count_successful,
    failed_targets, resolve_build_profile,
};
use crate::cli::Result;
use crate::config::Config;
use crate::pack::PackError;

pub enum BuildPlatform {
    Apple,
    Android,
    Wasm,
    Dart,
    All,
}

pub struct BuildCommandOptions {
    pub platform: BuildPlatform,
    pub release: bool,
    pub cargo_args: Vec<String>,
}

pub fn run_build(config: &Config, options: BuildCommandOptions) -> Result<Vec<BuildResult>> {
    let BuildCommandOptions {
        platform,
        release,
        cargo_args: cli_cargo_args,
    } = options;

    let cargo_args: Vec<String> = config
        .cargo_args_for_command("build")
        .into_iter()
        .chain(cli_cargo_args)
        .collect();

    let build_profile = resolve_build_profile(release, &cargo_args);

    let profile = build_profile.output_directory_name();

    let results = match platform {
        BuildPlatform::Apple => {
            if !config.is_apple_enabled() {
                return Ok(Vec::new());
            }
            println!("Building for Apple ({})...", profile);
            apple_builder(config, release, cargo_args.clone())?
                .build_targets(&config.apple_targets())?
        }
        BuildPlatform::Android => {
            if !config.is_android_enabled() {
                return Ok(Vec::new());
            }
            println!("Building for Android ({})...", profile);
            plain_builder(config, release, cargo_args.clone())
                .build_android(&config.android_targets())?
        }
        BuildPlatform::Wasm => {
            if !config.is_wasm_enabled() {
                return Ok(Vec::new());
            }
            println!("Building for wasm ({})...", profile);
            plain_builder(config, release, cargo_args.clone())
                .build_wasm_with_triple(config.wasm_triple())?
        }
        BuildPlatform::Dart => {
            if !config.is_dart_enabled() {
                return Ok(Vec::new());
            }
            println!("Building for dart ({})...", profile);
            plain_builder(config, release, cargo_args.clone())
                .build_targets(&config.dart_targets())?
        }
        BuildPlatform::All => {
            println!("Building all targets ({})...", profile);
            let mut all_results = Vec::new();
            if config.is_apple_enabled() {
                all_results.extend(
                    apple_builder(config, release, cargo_args.clone())?
                        .build_targets(&config.apple_targets())?,
                );
            }
            if config.is_android_enabled() {
                all_results.extend(
                    plain_builder(config, release, cargo_args.clone())
                        .build_android(&config.android_targets())?,
                );
            }
            if config.is_wasm_enabled() {
                all_results.extend(
                    plain_builder(config, release, cargo_args.clone())
                        .build_wasm_with_triple(config.wasm_triple())?,
                );
            }
            if config.is_dart_enabled() {
                all_results.extend(
                    plain_builder(config, release, cargo_args.clone())
                        .build_targets(&config.dart_targets())?,
                );
            }
            all_results
        }
    };

    if results.is_empty() {
        println!("No enabled targets matched the requested platform");
        return Ok(results);
    }

    print_build_results(&results);

    if all_successful(&results) {
        Ok(results)
    } else {
        Err(PackError::BuildFailed {
            targets: failed_targets(&results),
        }
        .into())
    }
}

fn apple_builder(config: &Config, release: bool, cargo_args: Vec<String>) -> Result<Builder<'_>> {
    let expansion = BindingExpansion::resolve(config, &cargo_args)?;
    Ok(Builder::new(
        config,
        build_options(release, cargo_args, expansion.env()?),
    ))
}

fn plain_builder(config: &Config, release: bool, cargo_args: Vec<String>) -> Builder<'_> {
    Builder::new(config, build_options(release, cargo_args, Vec::new()))
}

fn build_options(
    release: bool,
    cargo_args: Vec<String>,
    env: Vec<(OsString, OsString)>,
) -> BuildOptions {
    BuildOptions {
        release,
        package: None,
        cargo_args,
        env,
        on_output: None,
    }
}

fn print_build_results(results: &[BuildResult]) {
    println!();

    results.iter().for_each(|result| {
        let icon = if result.success { "[ok]" } else { "[failed]" };
        println!("  {} {}", icon, result.triple);
    });

    println!();

    let success_count = count_successful(results);
    let total = results.len();

    if all_successful(results) {
        println!("Built {}/{} targets successfully", success_count, total);
    } else {
        println!(
            "Built {}/{} targets ({} failed)",
            success_count,
            total,
            total - success_count
        );
        println!();
        println!("Failed targets:");
        failed_targets(results).iter().for_each(|triple| {
            println!("  - {}", triple);
        });
    }
}