use anyhow::{anyhow, Context, Result};
use clap::Args;
use std::path::PathBuf;
use whisker_build::Profile;
#[derive(Args, Debug)]
pub struct IosArgs {
#[arg(long)]
workspace: PathBuf,
#[arg(long)]
package: String,
#[arg(long)]
configuration: String,
#[arg(long)]
platform: String,
#[arg(long)]
archs: String,
#[arg(long)]
built_products_dir: PathBuf,
#[arg(long)]
features: Vec<String>,
}
#[derive(Args, Debug)]
pub struct AndroidArgs {
#[arg(long)]
workspace: PathBuf,
#[arg(long)]
package: String,
#[arg(long)]
profile: String,
#[arg(long)]
abi: String,
#[arg(long)]
jni_libs_dir: PathBuf,
#[arg(long, default_value = "24")]
min_sdk: u32,
#[arg(long)]
features: Vec<String>,
}
#[derive(Args, Debug)]
pub struct ModulesArgs {
#[arg(long)]
workspace: PathBuf,
#[arg(long)]
package: String,
}
fn canonicalize_workspace(p: &PathBuf) -> Result<PathBuf> {
std::fs::canonicalize(p).with_context(|| format!("canonicalize workspace {}", p.display()))
}
pub fn run_modules(args: ModulesArgs) -> Result<()> {
let workspace = canonicalize_workspace(&args.workspace)?;
let report = whisker_build::modules::build_modules_report(&workspace, &args.package)
.with_context(|| {
format!(
"build modules report for `{}` (workspace={})",
args.package,
workspace.display(),
)
})?;
let json = serde_json::to_string_pretty(&report).context("serialize modules report")?;
println!("{json}");
Ok(())
}
pub fn run_ios(args: IosArgs) -> Result<()> {
let workspace = canonicalize_workspace(&args.workspace)?;
let archs: Vec<&str> = args.archs.split_whitespace().collect();
let fw = whisker_build::ios::build_framework_for_xcode_run_script(
&whisker_build::ios::XcodeRunScriptInputs {
workspace_root: &workspace,
package: &args.package,
platform: &args.platform,
archs: &archs,
features: &args.features,
},
&args.built_products_dir,
)
.with_context(|| {
format!(
"build framework for ({}/{}) → {}",
args.platform,
args.archs,
args.built_products_dir.display(),
)
})?;
eprintln!(
"[whisker build-ios] published {} (configuration={}, archs=[{}])",
fw.display(),
args.configuration,
args.archs,
);
Ok(())
}
pub fn run_android(args: AndroidArgs) -> Result<()> {
let workspace = canonicalize_workspace(&args.workspace)?;
let cargo_toml = workspace.join("Cargo.toml");
let modules = whisker_build::modules::discover(&cargo_toml, &args.package)
.with_context(|| format!("discover whisker modules in {}", cargo_toml.display()))?;
let profile = parse_profile(&args.profile)?;
let toolchain = whisker_build::android::resolve_toolchain(&args.abi, args.min_sdk)
.with_context(|| {
format!(
"resolve NDK toolchain for {} (api {})",
args.abi, args.min_sdk
)
})?;
let so_path = whisker_build::android::cargo_build_dylib(&whisker_build::android::CargoBuild {
workspace_root: &workspace,
package: &args.package,
toolchain: &toolchain,
profile,
features: &args.features,
capture: None,
})
.context("cargo cross-compile for Android")?;
whisker_build::android::stage_so_files(&args.jni_libs_dir, &so_path, &toolchain, &args.abi)
.with_context(|| {
format!(
"stage .so + libc++_shared.so into {}",
args.jni_libs_dir.display()
)
})?;
eprintln!(
"[whisker build-android] {} module(s) discovered (gradle-subproject wiring is the Gradle plugin's job)",
modules.len(),
);
Ok(())
}
fn parse_profile(s: &str) -> Result<Profile> {
match s {
"debug" => Ok(Profile::Debug),
"release" => Ok(Profile::Release),
other => Err(anyhow!(
"--profile must be 'debug' or 'release' (got `{other}`)"
)),
}
}