use std::path::PathBuf;
use clap::{Args as ClapArgs, ValueEnum};
use color_eyre::eyre::{Result, bail};
use crate::shell::{self, display_output};
use crate::{header, success};
use waterui_cli::{
android::platform::AndroidPlatform, apple::platform::ApplePlatform, build::BuildOptions,
platform::PackageOptions, platform::Platform, project::Project, toolchain::Toolchain,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum TargetPlatform {
Ios,
IosSimulator,
Android,
Macos,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum AndroidArch {
Arm64,
X86_64,
Armv7,
X86,
}
impl AndroidArch {
const fn to_abi(self) -> &'static str {
match self {
Self::Arm64 => "arm64-v8a",
Self::X86_64 => "x86_64",
Self::Armv7 => "armeabi-v7a",
Self::X86 => "x86",
}
}
}
#[derive(ClapArgs, Debug)]
pub struct Args {
#[arg(short, long, value_enum)]
platform: TargetPlatform,
#[arg(long)]
release: bool,
#[arg(long)]
distribution: bool,
#[arg(long, default_value = ".")]
path: PathBuf,
#[arg(long, value_enum, value_delimiter = ',')]
arch: Vec<AndroidArch>,
}
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?;
if args.platform == TargetPlatform::Android && args.arch.is_empty() {
bail!(
"Android platform requires --arch flag.\n\
Examples:\n \
water package --platform android --arch arm64\n \
water package --platform android --arch arm64,x86_64"
);
}
let mode = if args.release { "release" } else { "debug" };
let dist = if args.distribution {
" (distribution)"
} else {
""
};
header!(
"Packaging {} for {} ({}){}",
project.crate_name(),
platform_name(args.platform),
mode,
dist
);
let spinner = shell::spinner("Checking toolchain...");
check_toolchain(args.platform).await?;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
success!("Toolchain ready");
let build_options = BuildOptions::new(args.release, false);
if args.platform == TargetPlatform::Android {
AndroidPlatform::clean_jni_libs(&project).await?;
for arch in &args.arch {
let spinner = shell::spinner(format!("Building Rust library ({})...", arch.to_abi()));
let platform = AndroidPlatform::from_abi(arch.to_abi());
display_output(platform.build(&project, build_options.clone())).await?;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
success!("Built for {}", arch.to_abi());
}
} else {
let spinner = shell::spinner("Building Rust library...");
display_output(build_for_platform(&project, args.platform, build_options)).await?;
if let Some(pb) = spinner {
pb.finish_and_clear();
}
success!("Built Rust library");
}
let spinner = shell::spinner("Packaging application...");
let package_options = PackageOptions::new(args.distribution, !args.release);
let artifact = match args.platform {
TargetPlatform::Android => {
let abis: Vec<&str> = args.arch.iter().map(|a| a.to_abi()).collect();
display_output(AndroidPlatform::package_with_abis(
&project,
package_options,
&abis,
))
.await?
}
_ => {
display_output(package_for_platform(
&project,
args.platform,
package_options,
))
.await?
}
};
if let Some(pb) = spinner {
pb.finish_and_clear();
}
success!("Packaged at {}", artifact.path().display());
Ok(())
}
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(())
}
async fn build_for_platform(
project: &Project,
platform: TargetPlatform,
options: BuildOptions,
) -> Result<PathBuf> {
match platform {
TargetPlatform::Ios => {
let p = ApplePlatform::ios();
Ok(p.build(project, options).await?)
}
TargetPlatform::IosSimulator => {
let p = ApplePlatform::ios_simulator();
Ok(p.build(project, options).await?)
}
TargetPlatform::Android => {
let p = AndroidPlatform::arm64();
Ok(p.build(project, options).await?)
}
TargetPlatform::Macos => {
let p = ApplePlatform::macos();
Ok(p.build(project, options).await?)
}
}
}
async fn package_for_platform(
project: &Project,
platform: TargetPlatform,
options: PackageOptions,
) -> Result<waterui_cli::device::Artifact> {
match platform {
TargetPlatform::Ios => {
let p = ApplePlatform::ios();
Ok(project.package(p, options).await?)
}
TargetPlatform::IosSimulator => {
let p = ApplePlatform::ios_simulator();
Ok(project.package(p, options).await?)
}
TargetPlatform::Android => {
let p = AndroidPlatform::arm64();
Ok(project.package(p, options).await?)
}
TargetPlatform::Macos => {
let p = ApplePlatform::macos();
Ok(project.package(p, options).await?)
}
}
}
const fn platform_name(platform: TargetPlatform) -> &'static str {
match platform {
TargetPlatform::Ios => "iOS",
TargetPlatform::IosSimulator => "iOS Simulator",
TargetPlatform::Android => "Android",
TargetPlatform::Macos => "macOS",
}
}