use std::path::{Path, PathBuf};
use color_eyre::eyre;
use serde::{Deserialize, Serialize};
use crate::{
android::backend::AndroidBackend,
apple::backend::AppleBackend,
build::BuildOptions,
device::Artifact,
esp32::backend::Esp32Backend,
gtk4::backend::Gtk4Backend,
hydrolysis::backend::HydrolysisBackend,
platform::{PackageOptions, TargetPlatform},
project::Project,
};
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Backends {
#[serde(default, skip_serializing_if = "String::is_empty")]
path: String,
android: Option<AndroidBackend>,
apple: Option<AppleBackend>,
gtk4: Option<Gtk4Backend>,
hydrolysis: Option<HydrolysisBackend>,
esp32: Option<Esp32Backend>,
}
impl Backends {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.android.is_none()
&& self.apple.is_none()
&& self.gtk4.is_none()
&& self.hydrolysis.is_none()
&& self.esp32.is_none()
}
#[cfg(test)]
pub(crate) fn set_esp32_for_tests(&mut self, backend: Esp32Backend) {
self.esp32 = Some(backend);
}
#[must_use]
pub const fn configures_backend_projects(&self) -> bool {
self.android.is_some()
|| self.apple.is_some()
|| self.gtk4.is_some()
|| self.hydrolysis.is_some()
}
#[must_use]
pub fn path(&self) -> &Path {
Path::new(&self.path)
}
pub fn set_path(&mut self, path: impl Into<String>) {
self.path = path.into();
}
#[must_use]
pub const fn android(&self) -> Option<&AndroidBackend> {
self.android.as_ref()
}
#[must_use]
pub const fn apple(&self) -> Option<&AppleBackend> {
self.apple.as_ref()
}
pub fn set_apple(&mut self, backend: AppleBackend) {
self.apple = Some(backend);
}
pub fn clear_apple(&mut self) {
self.apple = None;
}
pub fn set_android(&mut self, backend: AndroidBackend) {
self.android = Some(backend);
}
pub fn clear_android(&mut self) {
self.android = None;
}
#[must_use]
pub const fn gtk4(&self) -> Option<&Gtk4Backend> {
self.gtk4.as_ref()
}
pub fn set_gtk4(&mut self, backend: Gtk4Backend) {
self.gtk4 = Some(backend);
}
pub fn clear_gtk4(&mut self) {
self.gtk4 = None;
}
#[must_use]
pub const fn hydrolysis(&self) -> Option<&HydrolysisBackend> {
self.hydrolysis.as_ref()
}
pub fn set_hydrolysis(&mut self, backend: HydrolysisBackend) {
self.hydrolysis = Some(backend);
}
pub fn clear_hydrolysis(&mut self) {
self.hydrolysis = None;
}
#[must_use]
pub const fn esp32(&self) -> Option<&Esp32Backend> {
self.esp32.as_ref()
}
pub fn set_esp32(&mut self, backend: Esp32Backend) {
self.esp32 = Some(backend);
}
pub fn clear_esp32(&mut self) {
self.esp32 = None;
}
}
#[derive(Debug, thiserror::Error)]
pub enum FailToInitBackend {
#[error("Failed to write template files: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid backend configuration: {0}")]
Config(#[source] color_eyre::eyre::Error),
}
pub trait Backend: Sized + Send + Sync {
const DEFAULT_PATH: &'static str;
const CACHE_PATHS: &'static [&'static str];
fn path(&self) -> &Path;
fn init(project: &Project) -> impl Future<Output = Result<Self, FailToInitBackend>> + Send;
fn supports(&self, platform: TargetPlatform) -> bool;
fn build(
&self,
project: &Project,
platform: TargetPlatform,
options: BuildOptions,
) -> impl Future<Output = eyre::Result<PathBuf>> + Send;
fn package(
&self,
project: &Project,
platform: TargetPlatform,
options: PackageOptions,
) -> impl Future<Output = eyre::Result<Artifact>> + Send;
fn clean(
&self,
project: &Project,
platform: TargetPlatform,
) -> impl Future<Output = eyre::Result<()>> + Send;
}
pub async fn reinit_backend<B: Backend>(project: &Project) -> Result<B, FailToInitBackend> {
let backend_path = project.backend_path::<B>();
if backend_path.exists() {
let cache_paths: std::collections::HashSet<&str> = B::CACHE_PATHS.iter().copied().collect();
let entries = std::fs::read_dir(&backend_path)?;
for entry in entries {
let entry = entry?;
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !cache_paths.contains(&*name_str) {
let path = entry.path();
if path.is_dir() {
std::fs::remove_dir_all(&path)?;
} else {
std::fs::remove_file(&path)?;
}
}
}
}
B::init(project).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn esp32_device_config_is_not_backend_project_configuration() {
let mut backends = Backends::default();
assert!(!backends.configures_backend_projects());
backends.set_esp32_for_tests(Esp32Backend::new());
assert!(!backends.configures_backend_projects());
assert!(!backends.is_empty());
backends.set_gtk4(Gtk4Backend::default());
assert!(backends.configures_backend_projects());
}
}