use std::path::{Path, PathBuf};
use color_eyre::eyre;
use serde::{Deserialize, Serialize};
use crate::{
apple::platform::{build_rust_lib, clean_apple, is_apple_platform, package_apple},
backend::Backend,
build::BuildOptions,
device::Artifact,
platform::{PackageOptions, TargetBackend, TargetPlatform},
project::Project,
project_types::CrateName,
templates::{self, TemplateContext},
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppleBackend {
#[serde(
default = "default_apple_project_path",
skip_serializing_if = "is_default_apple_project_path"
)]
pub project_path: PathBuf,
pub scheme: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub revision: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub backend_path: Option<String>,
}
pub fn apple_product_name(project: &Project) -> Result<&str, eyre::Report> {
let name = project.manifest().package.name.as_str();
if name.is_empty() {
eyre::bail!("This project has no name; `package.name` in Water.toml names the application");
}
if name.contains(std::path::MAIN_SEPARATOR) || name.contains('/') {
eyre::bail!(
"The project name {name:?} contains a path separator, so it cannot name an application bundle"
);
}
Ok(name)
}
impl AppleBackend {
#[must_use]
pub fn new(scheme: impl Into<String>) -> Self {
Self {
project_path: default_apple_project_path(),
scheme: scheme.into(),
branch: None,
revision: None,
backend_path: None,
}
}
#[must_use]
pub fn with_project_path(mut self, path: impl Into<PathBuf>) -> Self {
self.project_path = path.into();
self
}
#[must_use]
pub fn with_backend_path(mut self, path: impl Into<String>) -> Self {
self.backend_path = Some(path.into());
self
}
#[must_use]
pub fn project_path(&self) -> &Path {
&self.project_path
}
}
fn default_apple_project_path() -> PathBuf {
PathBuf::from("apple")
}
fn is_default_apple_project_path(s: &Path) -> bool {
s == Path::new("apple")
}
impl Backend for AppleBackend {
const DEFAULT_PATH: &'static str = "apple";
const CACHE_PATHS: &'static [&'static str] = &["DerivedData"];
fn path(&self) -> &Path {
&self.project_path
}
async fn init(project: &Project) -> Result<Self, crate::backend::FailToInitBackend> {
let manifest = project.manifest();
let effective_waterui_path = manifest.waterui_path.clone();
let is_playground =
manifest.package.package_type == crate::project::PackageType::Playground;
let (scheme, app_name, crate_name_for_template) = if is_playground {
(
"WaterUIApp".to_string(),
"WaterUIApp".to_string(),
CrateName::try_from("WaterUIApp").expect("playground crate name must be valid"),
)
} else {
let crate_name = project.crate_name().clone();
let app_name = crate_name
.as_str()
.split('-')
.map(|s| {
let mut chars = s.chars();
chars.next().map_or_else(String::new, |first| {
first.to_uppercase().chain(chars).collect()
})
})
.collect::<String>();
(crate_name.to_string(), app_name, crate_name)
};
let project_path = default_apple_project_path();
let ios_permissions = manifest
.permissions
.iter()
.filter(|(_, entry)| entry.is_enabled())
.filter_map(|(key, entry)| {
key.ios_plist_key()
.map(|plist_key| templates::IosPermissionTemplateEntry {
plist_key,
description: entry.description().to_string(),
})
})
.collect();
let webview_enabled = project
.links_runtime_package("waterui-webview")
.await
.map_err(crate::backend::FailToInitBackend::Config)?;
let chromium_enabled = project
.links_runtime_package("waterui-chromium")
.await
.map_err(crate::backend::FailToInitBackend::Config)?;
let browser_engine = project
.linked_browser_engine()
.await
.map_err(crate::backend::FailToInitBackend::Config)?;
let ctx =
TemplateContext::for_project_manifest(manifest, crate_name_for_template, app_name)
.with_backend_project_path(project.backend_path::<Self>())
.with_project_root_path(project.root().to_path_buf())
.with_ios_permissions(ios_permissions)
.with_webview_enabled(webview_enabled)
.with_chromium_enabled(chromium_enabled)
.with_browser_engine(browser_engine);
templates::apple::scaffold(&project.backend_path::<Self>(), &ctx)
.await
.map_err(crate::backend::FailToInitBackend::Io)?;
Ok(Self {
project_path,
scheme,
branch: None,
revision: None,
backend_path: effective_waterui_path,
})
}
fn supports(&self, platform: TargetPlatform) -> bool {
is_apple_platform(platform)
}
async fn build(
&self,
project: &Project,
platform: TargetPlatform,
options: BuildOptions,
) -> eyre::Result<PathBuf> {
project
.browser_runtime_plan(platform, TargetBackend::Apple)
.await?;
build_rust_lib(project, platform, options).await
}
async fn package(
&self,
project: &Project,
platform: TargetPlatform,
options: PackageOptions,
) -> eyre::Result<Artifact> {
package_apple(project, platform, options).await
}
async fn clean(&self, project: &Project, _platform: TargetPlatform) -> eyre::Result<()> {
clean_apple(project).await
}
}