Skip to content

Commit 78ea10e

Browse files
committed
feat(runner): log simctl app outputs like print
1 parent 5a4a79b commit 78ea10e

File tree

9 files changed

+167
-48
lines changed

9 files changed

+167
-48
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ edition = "2021"
66
[features]
77
default = [ ]
88
xcodegen = [ "async", "dirs" ]
9-
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs", "async-stream", "tokio-stream", "strum", "simctl" ]
9+
daemon = [ "serial", "compilation", "logging", "async", "watcher", "proc", "xcodegen", "parity-tokio-ipc", "nvim-rs", "strum", "simctl" ]
1010
server = [ "serial", "logging", "dirs", "bsp-server", "url", "wax", "shell-words", "compilation", "strum" ]
1111
lua = [ "mlua", "serial", "strum", "simctl" ]
1212
serial = [ "serde", "serde_json", "serde_yaml" ]
1313
compilation = [ "serial", "lazy_static", "shell-words", "xcode" ]
1414
logging = [ "tracing", "tracing-appender", "tracing-subscriber" ]
15-
async = [ "tokio", "async-trait" ]
15+
async = [ "tokio", "async-trait", "futures", "async-stream", "tokio-stream" ]
1616
proc = [ "libproc" ]
1717
watcher = [ "dirs", "notify", "wax" ]
1818
jsonrpc = [ "serial" ]
@@ -50,6 +50,9 @@ serde_yaml = { version = "0.8.23", optional = true }
5050
tokio = { version = "1.17.0", features = ["full"], optional = true }
5151
tokio-util = { version = "0.7.1", features = ["codec"], optional = true }
5252
async-trait = { version = "0.1.52", optional = true }
53+
tokio-stream = { version = "0.1.8", features = ["io-util"], optional = true }
54+
async-stream = { version = "0.3.3", optional = true }
55+
futures = { version = "0.3.21", optional = true }
5356
# Logging Feature
5457
tracing = { version = "0.1.32", optional = true }
5558
tracing-subscriber = { version = "0.3.9", features = ["env-filter"], optional = true}
@@ -71,7 +74,6 @@ url = { version = "2.2.2", features = ["serde"], optional = t
7174
nvim-rs = { version = "0.4.0", optional = true, features = ["use_tokio"] }
7275
parity-tokio-ipc = { version = "0.9.0", optional = true }
7376
strum = { version = "0.24.0", features = ["derive"], optional = true }
74-
tokio-stream = { version = "0.1.8", features = ["io-util"], optional = true }
75-
async-stream = { version = "0.3.3", optional = true }
7677
simctl = { path = "../../../sources/simctl/", optional = true }
7778
thiserror = "1.0.31"
79+
libc = "0.2.126"

src/daemon/requests/run.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ impl Handler for Run {
7070
return Err(Error::Build(msg));
7171
}
7272

73+
// TODO(daemon): insert handler to state.runners
74+
// TODO(nvim): provide mapping to close runners.
75+
//
76+
// If there is more then one runner then pick, else close from current buffer.
77+
// C-c in normal/insert mode should close that process
7378
Runner {
7479
target: self.config.target,
7580
platform,

src/nvim.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ impl NvimClient {
7070
direction.clone(),
7171
)
7272
}
73+
74+
pub fn new_unamed_logger<'a>(&'a self) -> Logger<'a> {
75+
Logger::new(self, "".into(), None)
76+
}
7377
}
7478

7579
#[cfg(feature = "daemon")]

src/runner.rs

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
mod simctl;
2+
23
pub use self::simctl::*;
34

45
use crate::{
56
constants::DAEMON_STATE,
67
nvim::BufferDirection,
78
state::State,
8-
types::{Client, Platform, SimDevice},
9-
util::fmt,
9+
types::{Client, Platform},
10+
util::{
11+
fmt,
12+
process::{self, Output},
13+
},
1014
Error, Result,
1115
};
1216
use {
@@ -94,46 +98,75 @@ impl Runner {
9498
target,
9599
state,
96100
udid,
97-
direction,
98101
..
99102
} = self;
100103

101-
let ref mut logger = state
102-
.clients
103-
.get(&client.pid)?
104-
.new_logger("Run", &target, &direction);
104+
let ref mut logger = state.clients.get(&client.pid)?.new_unamed_logger();
105105
let app_id = settings.product_bundle_identifier;
106106
let path_to_app = settings.metal_library_output_dir;
107107

108108
logger.set_running().await?;
109-
let _runner = {
110-
let device = get_device(&state, udid)?;
109+
110+
let runner = {
111+
let device = if let Some(udid) = udid {
112+
state.devices.iter().find(|d| d.udid == udid).cloned()
113+
} else {
114+
None
115+
}
116+
.ok_or_else(|| Error::Run("udid not found!!".to_string()))?;
117+
111118
let runner = SimDeviceRunner::new(device, target.clone(), app_id, path_to_app);
112119
runner.boot(logger).await?;
113120
runner.install(logger).await?;
114-
runner.launch(logger).await?;
115121
runner
116122
};
117123

118-
// NOTE: This is required so when neovim exist this should also exit
124+
let child = runner.launch(logger).await?;
125+
let mut stream = process::stream(child)?;
126+
// TODO(daemon): ensure Simulator.app is running
127+
128+
logger.log(fmt::separator()).await?;
129+
130+
// TODO(nvim): Close ran simctl process on exit.
119131
tokio::spawn(async move {
120-
let state = DAEMON_STATE.clone().lock_owned().await;
121-
let nvim = state.clients.get(&client.pid)?;
122-
let ref mut _logger = nvim.new_logger("Run", &target, &direction);
132+
while let Some(output) = stream.next().await {
133+
let state = DAEMON_STATE.clone();
134+
let state = state.lock().await;
135+
let mut logger = match state.clients.get(&client.pid) {
136+
Ok(nvim) => nvim.new_unamed_logger(),
137+
Err(e) => {
138+
tracing::error!("SimDevice runner: {e}");
139+
break;
140+
}
141+
};
123142

124-
state.sync_client_state().await?;
143+
match output {
144+
Output::Out(msg) => {
145+
if !msg.contains("ignoring singular matrix") {
146+
logger.log(msg).await?;
147+
}
148+
}
149+
Output::Err(msg) => {
150+
logger.log(format!("[Error] {msg}")).await?;
151+
}
152+
Output::Exit(status) => {
153+
if let Ok(Some(code)) = status {
154+
logger.log(format!("[Exit] {code}")).await?;
155+
} else {
156+
logger
157+
.log(format!("[Error] Unable to get exit code"))
158+
.await?;
159+
}
160+
break;
161+
}
162+
};
163+
drop(state);
164+
}
165+
166+
drop(stream);
125167

126168
Ok(())
127169
})
128170
.pipe(Ok)
129171
}
130172
}
131-
132-
fn get_device<'a>(state: &'a OwnedMutexGuard<State>, udid: Option<String>) -> Result<SimDevice> {
133-
if let Some(udid) = udid {
134-
state.devices.iter().find(|d| d.udid == udid).cloned()
135-
} else {
136-
None
137-
}
138-
.ok_or_else(|| Error::Run("udid not found!!".to_string()))
139-
}

src/runner/simctl.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use crate::{nvim::Logger, types::SimDevice, Error, Result};
2-
use std::path::PathBuf;
2+
use std::{path::PathBuf, process::Stdio};
33
use tap::Pipe;
4+
use tokio::process::{Child, Command};
45

56
/// SimDevice ruuner
67
pub struct SimDeviceRunner {
78
device: SimDevice,
89
target: String,
910
app_id: String,
1011
path_to_app: PathBuf,
11-
stdout_path: String,
12-
stderr_path: String,
1312
}
1413

1514
impl SimDeviceRunner {
@@ -36,18 +35,25 @@ impl SimDeviceRunner {
3635
Ok(())
3736
}
3837

39-
pub async fn launch<'a>(&self, logger: &mut Logger<'a>) -> Result<()> {
38+
pub async fn launch<'a>(&self, logger: &mut Logger<'a>) -> Result<Child> {
4039
logger.log(self.launching_msg()).await?;
41-
self.device
42-
.launch(&self.app_id)
43-
.stdout(&self.stdout_path)
44-
.stderr(&self.stderr_path)
45-
.exec()
46-
.pipe(|res| self.ok_or_abort(res, logger))
47-
.await?;
40+
let process = Command::new("xcrun")
41+
.arg("simctl")
42+
.arg("launch")
43+
.arg("--terminate-running-process")
44+
.arg("--console")
45+
.arg(&self.device.udid)
46+
.arg(&self.app_id)
47+
.stdout(Stdio::piped())
48+
.stderr(Stdio::piped())
49+
.stdin(Stdio::null())
50+
.kill_on_drop(true)
51+
.spawn()?;
52+
4853
logger.log(self.connected_msg()).await?;
49-
Ok(())
54+
Ok(process)
5055
}
56+
5157
async fn ok_or_abort<'a, T>(
5258
&self,
5359
res: simctl::Result<T>,
@@ -84,10 +90,6 @@ impl SimDeviceRunner {
8490

8591
impl SimDeviceRunner {
8692
pub fn new(device: SimDevice, target: String, app_id: String, path_to_app: PathBuf) -> Self {
87-
let out_path = |out| format!("/tmp/{}_{out}_{}_runner.log", target, &device.udid).into();
88-
let stdout_path = out_path("stdout");
89-
let stderr_path = out_path("stderr");
90-
9193
tracing::debug!(
9294
"SimDeviceRunner: {}: {app_id} [{path_to_app:?}]",
9395
device.name
@@ -97,8 +99,6 @@ impl SimDeviceRunner {
9799
target,
98100
app_id,
99101
path_to_app,
100-
stdout_path,
101-
stderr_path,
102102
}
103103
}
104104
}

src/util/fmt.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ pub fn as_section(content: String) -> String {
1515
content.push_str("]");
1616
content
1717
}
18+
19+
pub fn separator() -> String {
20+
"-".repeat(73)
21+
}

src/util/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ pub mod serde;
1010
pub mod tracing;
1111

1212
pub mod fmt;
13+
14+
#[cfg(feature = "daemon")]
15+
pub mod process;

src/util/process.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Helper type for processing process output and exit status in non-blocking way
2+
// use futures::io::Chain;
3+
use futures::stream::{once, Stream, StreamExt};
4+
use std::{fmt::Display, io};
5+
use tokio::{
6+
io::{AsyncBufReadExt, AsyncRead, BufReader},
7+
process::Child,
8+
};
9+
10+
/// Create new process from [`Child`]
11+
/// Child must set stdout and stderr or it might panic.
12+
pub fn stream(mut child: Child) -> io::Result<impl Stream<Item = Output> + Send> {
13+
let stdout = child.stdout.take().unwrap();
14+
let stderr = child.stderr.take().unwrap();
15+
let status = tokio::spawn(async move { child.wait().await });
16+
17+
fn to_output(v: io::Result<String>, is_stdout: bool) -> Output {
18+
match v {
19+
Ok(line) if is_stdout => Output::Out(line),
20+
Ok(line) => Output::Err(line),
21+
Err(e) => Output::Err(e.to_string()),
22+
}
23+
}
24+
25+
use tokio_stream::wrappers::LinesStream;
26+
fn to_stream<R: AsyncRead>(out: R) -> LinesStream<BufReader<R>> {
27+
LinesStream::new(BufReader::new(out).lines())
28+
}
29+
30+
let stdout_stream = to_stream(stdout).map(|v| to_output(v, true));
31+
let stderr_stream = to_stream(stderr).map(|v| to_output(v, false));
32+
let exit_stream = once(async move {
33+
match status.await {
34+
Err(err) => Output::Err(err.to_string()),
35+
Ok(Ok(status)) => Output::Exit(Ok(status.code())),
36+
Ok(Err(err)) => Output::Exit(Err(err)),
37+
}
38+
});
39+
let stream = tokio_stream::StreamExt::merge(stdout_stream, stderr_stream)
40+
.chain(exit_stream)
41+
.boxed();
42+
43+
Ok(stream)
44+
}
45+
46+
/// Output produced by [`process`]
47+
#[derive(Debug)]
48+
pub enum Output {
49+
/// Source stdout
50+
Out(String),
51+
/// Source stderr or internal io::Error
52+
Err(String),
53+
/// Exit status
54+
Exit(Result<Option<i32>, io::Error>),
55+
}
56+
57+
impl Display for Output {
58+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59+
match self {
60+
Output::Out(msg) => msg.fmt(f),
61+
Output::Err(msg) => write!(f, "[Error] {msg}"),
62+
Output::Exit(Ok(Some(code))) => code.fmt(f),
63+
_ => Ok(()),
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)