1use std::{
4 collections::HashMap,
5 fmt::Debug,
6 path::{Path, PathBuf},
7};
8
9use color_eyre::eyre;
10use smol::{
11 channel::{Receiver, Sender, unbounded},
12 stream::Stream,
13};
14
15use crate::platform::Platform;
16
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
19pub enum LogLevel {
20 Error,
22 Warn,
24 #[default]
26 Info,
27 Debug,
29 Verbose,
31}
32
33impl LogLevel {
34 #[must_use]
36 pub const fn to_android_priority(self) -> char {
37 match self {
38 Self::Error => 'E',
39 Self::Warn => 'W',
40 Self::Info => 'I',
41 Self::Debug => 'D',
42 Self::Verbose => 'V',
43 }
44 }
45
46 #[must_use]
55 pub const fn to_apple_level(self) -> &'static str {
56 match self {
57 Self::Error | Self::Warn | Self::Info => "default",
58 Self::Debug | Self::Verbose => "debug",
59 }
60 }
61}
62
63#[derive(Debug, Clone, Default)]
65pub struct RunOptions {
66 env_vars: HashMap<String, String>,
74
75 log_level: Option<LogLevel>,
77}
78
79impl RunOptions {
80 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn insert_env_var(&mut self, key: String, value: String) {
88 self.env_vars.insert(key, value);
89 }
90
91 pub fn env_vars(&self) -> impl Iterator<Item = (&str, &str)> {
93 self.env_vars.iter().map(|(k, v)| (k.as_str(), v.as_str()))
94 }
95
96 pub const fn set_log_level(&mut self, level: LogLevel) {
98 self.log_level = Some(level);
99 }
100
101 #[must_use]
103 pub const fn log_level(&self) -> Option<LogLevel> {
104 self.log_level
105 }
106}
107
108#[derive(Debug)]
110pub struct Artifact {
111 bundle_id: String,
112 path: PathBuf,
113}
114
115impl Artifact {
116 #[must_use]
118 pub fn new(bundle_id: impl Into<String>, path: PathBuf) -> Self {
119 Self {
120 bundle_id: bundle_id.into(),
121 path,
122 }
123 }
124
125 #[must_use]
127 pub const fn bundle_id(&self) -> &str {
128 self.bundle_id.as_str()
129 }
130
131 #[must_use]
133 pub fn path(&self) -> &Path {
134 &self.path
135 }
136}
137
138pub trait Device: Send {
140 type Platform: Platform;
142 fn launch(&self) -> impl Future<Output = eyre::Result<()>> + Send;
146
147 fn run(
149 &self,
150 artifact: Artifact,
151 options: RunOptions,
152 ) -> impl Future<Output = Result<Running, FailToRun>> + Send;
153
154 fn platform(&self) -> Self::Platform;
156}
157
158pub struct Running {
162 sender: Sender<DeviceEvent>,
163 receiver: Receiver<DeviceEvent>,
164 on_drop: Vec<Box<dyn FnOnce() + Send>>,
165}
166
167impl Debug for Running {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("Running").finish_non_exhaustive()
170 }
171}
172
173impl Running {
174 #[allow(clippy::missing_panics_doc)]
176 pub fn new(on_drop: impl FnOnce() + Send + 'static) -> (Self, Sender<DeviceEvent>) {
177 let (sender, receiver) = unbounded();
178 sender.try_send(DeviceEvent::Started).unwrap(); (
180 Self {
181 sender: sender.clone(),
182 receiver,
183 on_drop: vec![Box::new(on_drop)],
184 },
185 sender,
186 )
187 }
188
189 pub fn retain<T: Send + 'static>(&mut self, value: T) {
191 self.on_drop.push(Box::new(move || {
192 drop(value);
193 }));
194 }
195}
196
197impl Stream for Running {
198 type Item = DeviceEvent;
199
200 fn poll_next(
201 self: std::pin::Pin<&mut Self>,
202 cx: &mut std::task::Context<'_>,
203 ) -> std::task::Poll<Option<Self::Item>> {
204 let receiver = unsafe { &mut self.get_unchecked_mut().receiver };
207 unsafe { std::pin::Pin::new_unchecked(receiver) }.poll_next(cx)
208 }
209}
210
211impl Drop for Running {
212 fn drop(&mut self) {
213 let _ = self.sender.try_send(DeviceEvent::Stopped);
214 for f in self.on_drop.drain(..) {
215 f();
216 }
217 }
218}
219
220#[derive(Debug, thiserror::Error)]
222pub enum FailToRun {
223 #[error("Invalid artifact")]
225 InvalidArtifact,
226
227 #[error("Failed to install application on device: {0}")]
229 Install(eyre::Report),
230
231 #[error("Failed to launch device: {0}")]
233 Launch(eyre::Report),
234 #[error("Failed to run application on device: {0}")]
236 Run(eyre::Report),
237
238 #[error("Failed to package the artifacts: {0}")]
240 Package(eyre::Report),
241
242 #[error("Failed to build the project: {0}")]
244 Build(eyre::Report),
245
246 #[error("Failed to start hot reload server: {0}")]
248 HotReload(crate::debug::hot_reload::FailToLaunch),
249
250 #[error("Application crashed: {0}")]
252 Crashed(String),
253}
254
255#[derive(Debug)]
257pub enum DeviceEvent {
258 Started,
260 Stopped,
262 Stdout {
264 message: String,
266 },
267
268 Stderr {
270 message: String,
272 },
273 Log {
275 level: tracing::Level,
277 message: String,
279 },
280
281 Exited,
283
284 Crashed(String),
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum DeviceKind {
291 Simulator,
293 Physical,
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum DeviceState {
300 Booted,
302 Shutdown,
304 Disconnected,
306}