dinghy-lib 0.8.4

Cross-compilation made easier - see main crate cargo-dinghy
Documentation
#![type_length_limit = "2149570"]
pub mod errors {
    pub use anyhow::{anyhow, bail, Context, Error, Result};
}

mod android;
#[cfg(target_os = "macos")]
mod apple;
pub mod config;
pub mod device;
mod host;
pub mod overlay;
pub mod platform;
pub mod plugin;
pub mod project;
mod script;
mod ssh;
mod toolchain;
pub mod utils;

pub use crate::config::Configuration;

#[cfg(target_os = "macos")]
use crate::apple::{IosManager, TvosManager, WatchosManager};
use crate::config::PlatformConfiguration;

use crate::platform::regular_platform::RegularPlatform;
use crate::project::Project;
use anyhow::{anyhow, Context};
use dyn_clone::DynClone;
use std::fmt::Display;
use std::{path, sync};

use crate::errors::Result;

pub struct Dinghy {
    devices: Vec<sync::Arc<Box<dyn Device>>>,
    platforms: Vec<(String, sync::Arc<Box<dyn Platform>>)>,
}

impl Dinghy {
    pub fn probe(conf: &sync::Arc<Configuration>) -> Result<Dinghy> {
        let mut managers: Vec<Box<dyn PlatformManager>> = vec![];
        if let Some(man) = host::HostManager::probe(conf) {
            managers.push(Box::new(man));
        }
        if let Some(man) = android::AndroidManager::probe() {
            managers.push(Box::new(man));
        }
        if let Some(man) = script::ScriptDeviceManager::probe(conf.clone()) {
            managers.push(Box::new(man));
        }
        if let Some(man) = ssh::SshDeviceManager::probe(conf.clone()) {
            managers.push(Box::new(man));
        }
        #[cfg(target_os = "macos")]
        {
            std::thread::sleep(std::time::Duration::from_millis(100));
            if let Some(man) = IosManager::new().context("Could not initialize iOS manager")? {
                managers.push(Box::new(man));
            }
            if let Some(man) = TvosManager::new().context("Could not initialize tvOS manager")? {
                managers.push(Box::new(man));
            }
            if let Some(man) = WatchosManager::new().context("Could not initialize tvOS manager")? {
                managers.push(Box::new(man));
            }
        }
        if let Some(man) = plugin::PluginManager::probe(conf.clone()) {
            managers.push(Box::new(man));
        }

        let mut devices = vec![];
        let mut platforms = vec![];
        for man in managers.into_iter() {
            devices.extend(
                man.devices()
                    .context("Could not list devices")?
                    .into_iter()
                    .map(|it| sync::Arc::new(it)),
            );
            platforms.extend(
                man.platforms()
                    .context("Could not list platforms")?
                    .into_iter()
                    .map(|it| (it.id(), sync::Arc::new(it))),
            );
        }
        for (platform_name, platform_conf) in &conf.platforms {
            if platform_name == "host" {
                continue;
            }
            let rustc_triple = platform_conf
                .rustc_triple
                .as_ref()
                .ok_or_else(|| anyhow!("Platform {} has no rustc_triple", platform_name))?;
            let pf = RegularPlatform::new(
                platform_conf.clone(),
                platform_name.to_string(),
                rustc_triple.clone(),
                platform_conf
                    .toolchain
                    .clone()
                    .map(|it| path::PathBuf::from(it))
                    .or(dirs::home_dir()
                        .map(|it| it.join(".dinghy").join("toolchain").join(platform_name)))
                    .with_context(|| format!("Toolchain missing for platform {}", platform_name))?,
            )
            .with_context(|| format!("Could not assemble platform {}", platform_name))?;
            platforms.push((pf.id(), sync::Arc::new(pf)));
        }
        Ok(Dinghy { devices, platforms })
    }

    pub fn devices(&self) -> Vec<sync::Arc<Box<dyn Device>>> {
        self.devices.clone()
    }

    pub fn host_platform(&self) -> sync::Arc<Box<dyn Platform>> {
        self.platforms[0].1.clone()
    }

    pub fn platforms(&self) -> Vec<sync::Arc<Box<dyn Platform>>> {
        self.platforms
            .iter()
            .map(|&(_, ref platform)| platform.clone())
            .collect()
    }

    pub fn platform_by_name(
        &self,
        platform_name_filter: &str,
    ) -> Option<sync::Arc<Box<dyn Platform>>> {
        self.platforms
            .iter()
            .filter(|&&(ref platform_name, _)| platform_name == platform_name_filter)
            .map(|&(_, ref platform)| platform.clone())
            .next()
    }
}

pub trait Device: std::fmt::Debug + Display + DeviceCompatibility + DynClone {
    fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()>;

    fn debug_app(
        &self,
        project: &Project,
        build: &Build,
        args: &[&str],
        envs: &[&str],
    ) -> Result<BuildBundle>;

    fn id(&self) -> &str;

    fn name(&self) -> &str;

    fn run_app(
        &self,
        project: &Project,
        build: &Build,
        args: &[&str],
        envs: &[&str],
    ) -> Result<BuildBundle>;
}

dyn_clone::clone_trait_object!(Device);

pub trait DeviceCompatibility {
    fn is_compatible_with_regular_platform(&self, _platform: &RegularPlatform) -> bool {
        false
    }

    fn is_compatible_with_host_platform(&self, _platform: &host::HostPlatform) -> bool {
        false
    }

    #[cfg(target_os = "macos")]
    fn is_compatible_with_simulator_platform(
        &self,
        _platform: &apple::AppleDevicePlatform,
    ) -> bool {
        false
    }
}

pub trait Platform: std::fmt::Debug {
    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()>;

    fn id(&self) -> String;

    fn is_compatible_with(&self, device: &dyn Device) -> bool;

    fn is_host(&self) -> bool;
    fn rustc_triple(&self) -> &str;

    fn strip(&self, build: &mut Build) -> Result<()>;
    fn sysroot(&self) -> Result<Option<path::PathBuf>>;
}

impl Display for dyn Platform {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "{}", self.id())
    }
}

pub trait PlatformManager {
    fn devices(&self) -> Result<Vec<Box<dyn Device>>>;
    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>>;
}

#[derive(Clone, Debug)]
pub struct Build {
    pub setup_args: SetupArgs,
    pub dynamic_libraries: Vec<path::PathBuf>,
    pub runnable: Runnable,
    pub target_path: path::PathBuf,
    pub files_in_run_args: Vec<path::PathBuf>,
}

#[derive(Clone, Debug)]
pub struct SetupArgs {
    pub verbosity: i8,
    pub forced_overlays: Vec<String>,
    pub envs: Vec<String>,
    pub cleanup: bool,
    pub strip: bool,
    pub device_id: Option<String>,
}

impl SetupArgs {
    pub fn get_runner_command(&self, platform_id: &str) -> String {
        let mut extra_args = String::new();
        if self.verbosity > 0 {
            for _ in 0..self.verbosity {
                extra_args.push_str("-v ")
            }
        }
        if self.verbosity < 0 {
            for _ in 0..-self.verbosity {
                extra_args.push_str("-q ")
            }
        }
        if self.cleanup {
            extra_args.push_str("--cleanup ")
        }
        if self.strip {
            extra_args.push_str("--strip ")
        }
        if let Some(device_id) = &self.device_id {
            extra_args.push_str("-d ");
            extra_args.push_str(&device_id);
            extra_args.push(' ');
        }
        for env in &self.envs {
            extra_args.push_str("-e ");
            extra_args.push_str(env);
            extra_args.push(' ');
        }

        format!(
            "{} -p {} {}runner --",
            std::env::current_exe().unwrap().to_str().unwrap(),
            platform_id,
            extra_args
        )
    }
}

#[derive(Clone, Debug, Default)]
pub struct BuildBundle {
    pub id: String,
    pub bundle_dir: path::PathBuf,
    pub bundle_exe: path::PathBuf,
    pub lib_dir: path::PathBuf,
    pub root_dir: path::PathBuf,
    pub app_id: Option<String>,
}

impl BuildBundle {
    fn replace_prefix_with<P: AsRef<path::Path>>(&self, path: P) -> Result<Self> {
        Ok(BuildBundle {
            bundle_dir: utils::normalize_path(
                &path
                    .as_ref()
                    .to_path_buf()
                    .join(self.bundle_dir.strip_prefix(&self.root_dir)?),
            ),
            bundle_exe: utils::normalize_path(
                &path
                    .as_ref()
                    .to_path_buf()
                    .join(self.bundle_exe.strip_prefix(&self.root_dir)?),
            ),
            lib_dir: utils::normalize_path(
                &path
                    .as_ref()
                    .to_path_buf()
                    .join(self.lib_dir.strip_prefix(&self.root_dir)?),
            ),
            root_dir: path.as_ref().to_path_buf(),
            ..self.clone()
        })
    }
}

#[derive(Clone, Debug, Default)]
pub struct Runnable {
    pub id: String,
    pub package_name: String,
    pub exe: path::PathBuf,
    pub source: path::PathBuf,
    pub skip_source_copy: bool,
}