use std::path::PathBuf;
use clap::{Args as ClapArgs, ValueEnum};
use color_eyre::eyre::{Result, bail};
use crate::shell::{self, display_output};
use crate::{error, header, success};
use waterui_cli::{
android::platform::AndroidPlatform, apple::platform::ApplePlatform, build::BuildOptions,
platform::Platform as _, project::Project, toolchain::Toolchain,
};
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum TargetPlatform {
Ios,
IosSimulator,
Android,
Macos,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum TargetArch {
Arm64,
X86_64,
Armv7,
X86,
}
#[derive(ClapArgs, Debug)]
pub struct Args {
#[arg(short, long, value_enum)]
platform: TargetPlatform,
#[arg(short, long, value_enum)]
arch: Option<TargetArch>,
#[arg(long)]
release: bool,
#[arg(long, default_value = ".")]
path: PathBuf,
#[arg(long)]
output_dir: Option<PathBuf>,
}
#[allow(clippy::too_many_lines)]
pub async fn run(args: Args) -> Result<()> {
let project_path = args
.path
.canonicalize()
.unwrap_or_else(|_| args.path.clone());
let project = Project::open(&project_path).await?;
let build_options = if let Some(ref output_dir) = args.output_dir {
BuildOptions::new(args.release, false).with_output_dir(output_dir)
} else {
BuildOptions::new(args.release, false)
};
let mode = if args.release { "release" } else { "debug" };
header!(
"Building {} for {} ({})",
project.crate_name(),
platform_name(args.platform),
mode
);
let spinner = shell::spinner("Checking toolchain...");
check_toolchain(args.platform).await?;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
success!("Toolchain ready");
let spinner = shell::spinner("Compiling Rust library...");
let result = display_output(async {
match (args.platform, args.arch) {
(TargetPlatform::Ios, None | Some(TargetArch::Arm64)) => {
ApplePlatform::ios().build(&project, build_options).await
}
(TargetPlatform::Ios, Some(arch)) => {
bail!("iOS physical devices only support arm64, not {:?}", arch)
}
(TargetPlatform::IosSimulator, None) => {
ApplePlatform::ios_simulator()
.build(&project, build_options)
.await
}
(TargetPlatform::IosSimulator, Some(TargetArch::Arm64)) => {
ApplePlatform::ios_simulator_arm64()
.build(&project, build_options)
.await
}
(TargetPlatform::IosSimulator, Some(TargetArch::X86_64)) => {
ApplePlatform::ios_simulator_x86_64()
.build(&project, build_options)
.await
}
(TargetPlatform::IosSimulator, Some(arch)) => {
bail!(
"iOS Simulator only supports arm64 or x86_64, not {:?}",
arch
)
}
(TargetPlatform::Android, None | Some(TargetArch::Arm64)) => {
AndroidPlatform::arm64()
.build(&project, build_options)
.await
}
(TargetPlatform::Android, Some(TargetArch::X86_64)) => {
AndroidPlatform::x86_64()
.build(&project, build_options)
.await
}
(TargetPlatform::Android, Some(TargetArch::Armv7)) => {
AndroidPlatform::from_abi("armeabi-v7a")
.build(&project, build_options)
.await
}
(TargetPlatform::Android, Some(TargetArch::X86)) => {
AndroidPlatform::from_abi("x86")
.build(&project, build_options)
.await
}
(TargetPlatform::Macos, None) => {
ApplePlatform::macos().build(&project, build_options).await
}
(TargetPlatform::Macos, Some(TargetArch::Arm64)) => {
ApplePlatform::macos_arm64()
.build(&project, build_options)
.await
}
(TargetPlatform::Macos, Some(TargetArch::X86_64)) => {
ApplePlatform::macos_x86_64()
.build(&project, build_options)
.await
}
(TargetPlatform::Macos, Some(arch)) => {
bail!("macOS only supports arm64 or x86_64, not {:?}", arch)
}
}
})
.await;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
match result {
Ok(lib_dir) => {
success!("Built library at {}", lib_dir.display());
if let Some(output_dir) = args.output_dir {
success!("Copied library to {}", output_dir.display());
}
Ok(())
}
Err(e) => {
error!("Build failed: {e}");
Err(e)
}
}
}
async fn check_toolchain(platform: TargetPlatform) -> Result<()> {
use waterui_cli::platform::Platform;
match platform {
TargetPlatform::Ios | TargetPlatform::IosSimulator | TargetPlatform::Macos => {
let platform = ApplePlatform::ios_simulator();
let toolchain = platform.toolchain();
if let Err(e) = toolchain.check().await {
bail!("Toolchain check failed: {e}");
}
}
TargetPlatform::Android => {
let platform = AndroidPlatform::arm64();
let toolchain = platform.toolchain();
if let Err(e) = toolchain.check().await {
bail!("Toolchain check failed: {e}");
}
}
}
Ok(())
}
const fn platform_name(platform: TargetPlatform) -> &'static str {
match platform {
TargetPlatform::Ios => "iOS",
TargetPlatform::IosSimulator => "iOS Simulator",
TargetPlatform::Android => "Android",
TargetPlatform::Macos => "macOS",
}
}