use anyhow::Result;
use clap::{arg, Arg, ArgMatches, Args, FromArgMatches};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::str::FromStr;
use target_lexicon::{Environment, OperatingSystem, Triple};
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[non_exhaustive]
pub(crate) enum Platform {
#[serde(rename = "web")]
Web,
#[serde(rename = "macos")]
MacOS,
#[serde(rename = "windows")]
Windows,
#[serde(rename = "linux")]
Linux,
#[serde(rename = "ios")]
Ios,
#[serde(rename = "android")]
Android,
#[serde(rename = "server")]
Server,
#[serde(rename = "liveview")]
Liveview,
#[default]
Unknown,
}
impl Platform {
fn from_identifier(identifier: &str) -> std::result::Result<Self, clap::Error> {
match identifier {
"web" => Ok(Self::Web),
"macos" => Ok(Self::MacOS),
"windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"ios" => Ok(Self::Ios),
"android" => Ok(Self::Android),
"server" => Ok(Self::Server),
"liveview" => Ok(Self::Liveview),
"desktop" => {
if cfg!(target_os = "macos") {
Ok(Self::MacOS)
} else if cfg!(target_os = "windows") {
Ok(Self::Windows)
} else if cfg!(unix) {
Ok(Self::Linux)
} else {
Err(clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
"Desktop alias is not supported on this platform",
))
}
}
_ => Err(clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Unknown platform: {identifier}"),
)),
}
}
}
impl Args for Platform {
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
fn augment_args(cmd: clap::Command) -> clap::Command {
const HELP_HEADING: &str = "Platform";
cmd.arg(arg!(--web "Target a web app").help_heading(HELP_HEADING))
.arg(arg!(--desktop "Target a desktop app").help_heading(HELP_HEADING))
.arg(arg!(--macos "Target a macos desktop app").help_heading(HELP_HEADING))
.arg(arg!(--windows "Target a windows desktop app").help_heading(HELP_HEADING))
.arg(arg!(--linux "Target a linux desktop app").help_heading(HELP_HEADING))
.arg(arg!(--ios "Target an ios app").help_heading(HELP_HEADING))
.arg(arg!(--android "Target an android app").help_heading(HELP_HEADING))
.arg(arg!(--server "Target a server build").help_heading(HELP_HEADING))
.arg(arg!(--liveview "Target a liveview build").help_heading(HELP_HEADING))
.arg(
Arg::new("platform")
.long("platform")
.value_name("PLATFORM")
.help("Manually set the platform (web, macos, windows, linux, ios, android, server, liveview)")
.help_heading(HELP_HEADING)
.value_parser([
"web", "macos", "windows", "linux", "ios", "android", "server", "liveview", "desktop",
])
.conflicts_with("target_alias"),
)
.group(
clap::ArgGroup::new("target_alias")
.args([
"web", "desktop", "macos", "windows", "linux", "ios", "android", "server",
"liveview",
])
.multiple(false)
.required(false),
)
}
}
impl FromArgMatches for Platform {
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
if let Some(identifier) = matches.get_one::<String>("platform") {
Self::from_identifier(identifier)
} else if let Some(platform) = matches.get_one::<clap::Id>("target_alias") {
Self::from_identifier(platform.as_str())
} else {
Ok(Self::Unknown)
}
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
*self = Self::from_arg_matches(matches)?;
Ok(())
}
}
#[derive(
Copy,
Clone,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Debug,
clap::ValueEnum,
)]
#[non_exhaustive]
pub(crate) enum Renderer {
Webview,
Native,
Server,
Liveview,
Web,
}
impl Renderer {
pub(crate) fn feature_name(&self, target: &Triple) -> &str {
match self {
Renderer::Webview => match (target.environment, target.operating_system) {
(Environment::Android, _) | (_, OperatingSystem::IOS(_)) => "mobile",
_ => "desktop",
},
Renderer::Native => "native",
Renderer::Server => "server",
Renderer::Liveview => "liveview",
Renderer::Web => "web",
}
}
pub(crate) fn autodetect_from_cargo_feature(feature: &str) -> Option<Self> {
match feature {
"web" => Some(Self::Web),
"desktop" | "mobile" => Some(Self::Webview),
"native" => Some(Self::Native),
"liveview" => Some(Self::Liveview),
"server" => Some(Self::Server),
_ => None,
}
}
}
#[derive(Debug)]
pub(crate) struct UnknownRendererError;
impl std::error::Error for UnknownRendererError {}
impl std::fmt::Display for UnknownRendererError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown renderer")
}
}
impl FromStr for Renderer {
type Err = UnknownRendererError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"webview" => Ok(Self::Webview),
"native" => Ok(Self::Native),
"server" => Ok(Self::Server),
"liveview" => Ok(Self::Liveview),
"web" => Ok(Self::Web),
_ => Err(UnknownRendererError),
}
}
}
impl Display for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Renderer::Webview => "webview",
Renderer::Native => "native",
Renderer::Server => "server",
Renderer::Liveview => "liveview",
Renderer::Web => "web",
})
}
}
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[non_exhaustive]
pub(crate) enum BundleFormat {
#[serde(rename = "web")]
#[default]
Web,
#[cfg_attr(target_os = "macos", serde(alias = "desktop"))]
#[serde(rename = "macos")]
MacOS,
#[cfg_attr(target_os = "windows", serde(alias = "desktop"))]
#[serde(rename = "windows")]
Windows,
#[cfg_attr(target_os = "linux", serde(alias = "desktop"))]
#[serde(rename = "linux")]
Linux,
#[serde(rename = "server")]
Server,
#[serde(rename = "ios")]
Ios,
#[serde(rename = "android")]
Android,
}
impl BundleFormat {
pub(crate) fn host() -> Self {
if cfg!(target_os = "macos") {
Self::MacOS
} else if cfg!(target_os = "windows") {
Self::Windows
} else if cfg!(target_os = "linux") {
Self::Linux
} else {
Self::Web
}
}
pub(crate) fn build_folder_name(&self) -> &'static str {
match self {
Self::Web => "web",
Self::Server => "web",
Self::Ios => "ios",
Self::Android => "android",
Self::Windows => "windows",
Self::Linux => "linux",
Self::MacOS => "macos",
}
}
pub(crate) fn profile_name(&self, release: bool) -> String {
let base_profile = match self {
Self::MacOS | Self::Windows | Self::Linux => "desktop",
Self::Web => "wasm",
Self::Ios => "ios",
Self::Android => "android",
Self::Server => "server",
};
let opt_level = if release { "release" } else { "dev" };
format!("{base_profile}-{opt_level}")
}
pub(crate) fn expected_name(&self) -> &'static str {
match self {
Self::Web => "Web",
Self::MacOS => "MacOS",
Self::Windows => "Windows",
Self::Linux => "Linux",
Self::Ios => "iOS",
Self::Android => "Android",
Self::Server => "Server",
}
}
}
#[derive(Debug)]
pub(crate) struct UnknownBundleFormatError;
impl std::error::Error for UnknownBundleFormatError {}
impl std::fmt::Display for UnknownBundleFormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown bundle format")
}
}
impl FromStr for BundleFormat {
type Err = UnknownBundleFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"macos" => Ok(Self::MacOS),
"windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"server" => Ok(Self::Server),
"ios" => Ok(Self::Ios),
"android" => Ok(Self::Android),
_ => Err(UnknownBundleFormatError),
}
}
}
impl Display for BundleFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
BundleFormat::Web => "web",
BundleFormat::MacOS => "macos",
BundleFormat::Windows => "windows",
BundleFormat::Linux => "linux",
BundleFormat::Server => "server",
BundleFormat::Ios => "ios",
BundleFormat::Android => "android",
})
}
}