dinghy_lib/
lib.rs

1#![type_length_limit = "2149570"]
2pub mod errors {
3    pub use anyhow::{anyhow, bail, Context, Error, Result};
4}
5
6mod android;
7#[cfg(target_os = "macos")]
8mod apple;
9pub mod config;
10pub mod device;
11mod host;
12pub mod overlay;
13pub mod platform;
14pub mod plugin;
15pub mod project;
16mod script;
17mod ssh;
18mod toolchain;
19pub mod utils;
20
21pub use crate::config::Configuration;
22
23#[cfg(target_os = "macos")]
24use crate::apple::{IosManager, TvosManager, WatchosManager};
25use crate::config::PlatformConfiguration;
26
27use crate::platform::regular_platform::RegularPlatform;
28use crate::project::Project;
29use anyhow::{anyhow, Context};
30use dyn_clone::DynClone;
31use std::fmt::Display;
32use std::{path, sync};
33
34use crate::errors::Result;
35
36pub struct Dinghy {
37    devices: Vec<sync::Arc<Box<dyn Device>>>,
38    platforms: Vec<(String, sync::Arc<Box<dyn Platform>>)>,
39}
40
41impl Dinghy {
42    pub fn probe(conf: &sync::Arc<Configuration>) -> Result<Dinghy> {
43        let mut managers: Vec<Box<dyn PlatformManager>> = vec![];
44        if let Some(man) = host::HostManager::probe(conf) {
45            managers.push(Box::new(man));
46        }
47        if let Some(man) = android::AndroidManager::probe() {
48            managers.push(Box::new(man));
49        }
50        if let Some(man) = script::ScriptDeviceManager::probe(conf.clone()) {
51            managers.push(Box::new(man));
52        }
53        if let Some(man) = ssh::SshDeviceManager::probe(conf.clone()) {
54            managers.push(Box::new(man));
55        }
56        #[cfg(target_os = "macos")]
57        {
58            std::thread::sleep(std::time::Duration::from_millis(100));
59            if let Some(man) = IosManager::new().context("Could not initialize iOS manager")? {
60                managers.push(Box::new(man));
61            }
62            if let Some(man) = TvosManager::new().context("Could not initialize tvOS manager")? {
63                managers.push(Box::new(man));
64            }
65            if let Some(man) = WatchosManager::new().context("Could not initialize tvOS manager")? {
66                managers.push(Box::new(man));
67            }
68        }
69        if let Some(man) = plugin::PluginManager::probe(conf.clone()) {
70            managers.push(Box::new(man));
71        }
72
73        let mut devices = vec![];
74        let mut platforms = vec![];
75        for man in managers.into_iter() {
76            devices.extend(
77                man.devices()
78                    .context("Could not list devices")?
79                    .into_iter()
80                    .map(|it| sync::Arc::new(it)),
81            );
82            platforms.extend(
83                man.platforms()
84                    .context("Could not list platforms")?
85                    .into_iter()
86                    .map(|it| (it.id(), sync::Arc::new(it))),
87            );
88        }
89        for (platform_name, platform_conf) in &conf.platforms {
90            if platform_name == "host" {
91                continue;
92            }
93            let rustc_triple = platform_conf
94                .rustc_triple
95                .as_ref()
96                .ok_or_else(|| anyhow!("Platform {} has no rustc_triple", platform_name))?;
97            let pf = RegularPlatform::new(
98                platform_conf.clone(),
99                platform_name.to_string(),
100                rustc_triple.clone(),
101                platform_conf
102                    .toolchain
103                    .clone()
104                    .map(|it| path::PathBuf::from(it))
105                    .or(dirs::home_dir()
106                        .map(|it| it.join(".dinghy").join("toolchain").join(platform_name)))
107                    .with_context(|| format!("Toolchain missing for platform {}", platform_name))?,
108            )
109            .with_context(|| format!("Could not assemble platform {}", platform_name))?;
110            platforms.push((pf.id(), sync::Arc::new(pf)));
111        }
112        Ok(Dinghy { devices, platforms })
113    }
114
115    pub fn devices(&self) -> Vec<sync::Arc<Box<dyn Device>>> {
116        self.devices.clone()
117    }
118
119    pub fn host_platform(&self) -> sync::Arc<Box<dyn Platform>> {
120        self.platforms[0].1.clone()
121    }
122
123    pub fn platforms(&self) -> Vec<sync::Arc<Box<dyn Platform>>> {
124        self.platforms
125            .iter()
126            .map(|&(_, ref platform)| platform.clone())
127            .collect()
128    }
129
130    pub fn platform_by_name(
131        &self,
132        platform_name_filter: &str,
133    ) -> Option<sync::Arc<Box<dyn Platform>>> {
134        self.platforms
135            .iter()
136            .filter(|&&(ref platform_name, _)| platform_name == platform_name_filter)
137            .map(|&(_, ref platform)| platform.clone())
138            .next()
139    }
140}
141
142pub trait Device: std::fmt::Debug + Display + DeviceCompatibility + DynClone {
143    fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()>;
144
145    fn debug_app(
146        &self,
147        project: &Project,
148        build: &Build,
149        args: &[&str],
150        envs: &[&str],
151    ) -> Result<BuildBundle>;
152
153    fn id(&self) -> &str;
154
155    fn name(&self) -> &str;
156
157    fn run_app(
158        &self,
159        project: &Project,
160        build: &Build,
161        args: &[&str],
162        envs: &[&str],
163    ) -> Result<BuildBundle>;
164}
165
166dyn_clone::clone_trait_object!(Device);
167
168pub trait DeviceCompatibility {
169    fn is_compatible_with_regular_platform(&self, _platform: &RegularPlatform) -> bool {
170        false
171    }
172
173    fn is_compatible_with_host_platform(&self, _platform: &host::HostPlatform) -> bool {
174        false
175    }
176
177    #[cfg(target_os = "macos")]
178    fn is_compatible_with_simulator_platform(
179        &self,
180        _platform: &apple::AppleDevicePlatform,
181    ) -> bool {
182        false
183    }
184}
185
186pub trait Platform: std::fmt::Debug {
187    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()>;
188
189    fn id(&self) -> String;
190
191    fn is_compatible_with(&self, device: &dyn Device) -> bool;
192
193    fn is_host(&self) -> bool;
194    fn rustc_triple(&self) -> &str;
195
196    fn strip(&self, build: &mut Build) -> Result<()>;
197    fn sysroot(&self) -> Result<Option<path::PathBuf>>;
198}
199
200impl Display for dyn Platform {
201    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
202        write!(fmt, "{}", self.id())
203    }
204}
205
206pub trait PlatformManager {
207    fn devices(&self) -> Result<Vec<Box<dyn Device>>>;
208    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>>;
209}
210
211#[derive(Clone, Debug)]
212pub struct Build {
213    pub setup_args: SetupArgs,
214    pub dynamic_libraries: Vec<path::PathBuf>,
215    pub runnable: Runnable,
216    pub target_path: path::PathBuf,
217    pub files_in_run_args: Vec<path::PathBuf>,
218}
219
220#[derive(Clone, Debug)]
221pub struct SetupArgs {
222    pub verbosity: i8,
223    pub forced_overlays: Vec<String>,
224    pub envs: Vec<String>,
225    pub cleanup: bool,
226    pub strip: bool,
227    pub device_id: Option<String>,
228}
229
230impl SetupArgs {
231    pub fn get_runner_command(&self, platform_id: &str) -> String {
232        let mut extra_args = String::new();
233        if self.verbosity > 0 {
234            for _ in 0..self.verbosity {
235                extra_args.push_str("-v ")
236            }
237        }
238        if self.verbosity < 0 {
239            for _ in 0..-self.verbosity {
240                extra_args.push_str("-q ")
241            }
242        }
243        if self.cleanup {
244            extra_args.push_str("--cleanup ")
245        }
246        if self.strip {
247            extra_args.push_str("--strip ")
248        }
249        if let Some(device_id) = &self.device_id {
250            extra_args.push_str("-d ");
251            extra_args.push_str(&device_id);
252            extra_args.push(' ');
253        }
254        for env in &self.envs {
255            extra_args.push_str("-e ");
256            extra_args.push_str(env);
257            extra_args.push(' ');
258        }
259
260        format!(
261            "{} -p {} {}runner --",
262            std::env::current_exe().unwrap().to_str().unwrap(),
263            platform_id,
264            extra_args
265        )
266    }
267}
268
269#[derive(Clone, Debug, Default)]
270pub struct BuildBundle {
271    pub id: String,
272    pub bundle_dir: path::PathBuf,
273    pub bundle_exe: path::PathBuf,
274    pub lib_dir: path::PathBuf,
275    pub root_dir: path::PathBuf,
276    pub app_id: Option<String>,
277}
278
279impl BuildBundle {
280    fn replace_prefix_with<P: AsRef<path::Path>>(&self, path: P) -> Result<Self> {
281        Ok(BuildBundle {
282            bundle_dir: utils::normalize_path(
283                &path
284                    .as_ref()
285                    .to_path_buf()
286                    .join(self.bundle_dir.strip_prefix(&self.root_dir)?),
287            ),
288            bundle_exe: utils::normalize_path(
289                &path
290                    .as_ref()
291                    .to_path_buf()
292                    .join(self.bundle_exe.strip_prefix(&self.root_dir)?),
293            ),
294            lib_dir: utils::normalize_path(
295                &path
296                    .as_ref()
297                    .to_path_buf()
298                    .join(self.lib_dir.strip_prefix(&self.root_dir)?),
299            ),
300            root_dir: path.as_ref().to_path_buf(),
301            ..self.clone()
302        })
303    }
304}
305
306#[derive(Clone, Debug, Default)]
307pub struct Runnable {
308    pub id: String,
309    pub package_name: String,
310    pub exe: path::PathBuf,
311    pub source: path::PathBuf,
312    pub skip_source_copy: bool,
313}