Skip to content

Commit 76d1eaa

Browse files
authored
feat(cli): debug command output in real time (#4318)
1 parent b9e1e90 commit 76d1eaa

8 files changed

Lines changed: 139 additions & 61 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"cli.rs": patch
3+
"cli.js": patch
4+
"tauri-bundler": patch
5+
---
6+
7+
Log command output in real time instead of waiting for it to finish.

tooling/bundler/src/bundle.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub use self::{
2525
use log::{info, warn};
2626
pub use settings::{WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
2727

28-
use std::path::PathBuf;
28+
use std::{fmt::Write, path::PathBuf};
2929

3030
/// Generated bundle metadata.
3131
#[derive(Debug)]
@@ -86,7 +86,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
8686
if bundle.package_type == crate::PackageType::Updater {
8787
note = " (updater)";
8888
}
89-
printable_paths.push_str(&format!(" {}{}\n", path.display(), note));
89+
writeln!(printable_paths, " {}{}", path.display(), note).unwrap();
9090
}
9191
}
9292

tooling/bundler/src/bundle/common.rs

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use log::debug;
77
use std::{
88
ffi::OsStr,
99
fs::{self, File},
10-
io::{self, BufWriter},
10+
io::{self, BufReader, BufWriter},
1111
path::Path,
12-
process::{Command, Output},
12+
process::{Command, Output, Stdio},
13+
sync::{Arc, Mutex},
1314
};
1415

1516
/// Returns true if the path has a filename indicating that it is a high-desity
@@ -139,25 +140,64 @@ pub trait CommandExt {
139140

140141
impl CommandExt for Command {
141142
fn output_ok(&mut self) -> crate::Result<Output> {
142-
debug!(action = "Running"; "Command `{} {}`", self.get_program().to_string_lossy(), self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{} {}", acc, arg)));
143+
let program = self.get_program().to_string_lossy().into_owned();
144+
debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{} {}", acc, arg)));
143145

144-
let output = self.output()?;
146+
self.stdout(Stdio::piped());
147+
self.stderr(Stdio::piped());
145148

146-
let stdout = String::from_utf8_lossy(&output.stdout);
147-
if !stdout.is_empty() {
148-
debug!("Stdout: {}", stdout);
149-
}
150-
let stderr = String::from_utf8_lossy(&output.stderr);
151-
if !stderr.is_empty() {
152-
debug!("Stderr: {}", stderr);
153-
}
149+
let mut child = self.spawn()?;
150+
151+
let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
152+
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
153+
let stdout_lines_ = stdout_lines.clone();
154+
std::thread::spawn(move || {
155+
let mut buf = Vec::new();
156+
let mut lines = stdout_lines_.lock().unwrap();
157+
loop {
158+
buf.clear();
159+
match tauri_utils::io::read_line(&mut stdout, &mut buf) {
160+
Ok(s) if s == 0 => break,
161+
_ => (),
162+
}
163+
debug!(action = "stdout"; "{}", String::from_utf8_lossy(&buf));
164+
lines.extend(buf.clone());
165+
lines.push(b'\n');
166+
}
167+
});
168+
169+
let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
170+
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
171+
let stderr_lines_ = stderr_lines.clone();
172+
std::thread::spawn(move || {
173+
let mut buf = Vec::new();
174+
let mut lines = stderr_lines_.lock().unwrap();
175+
loop {
176+
buf.clear();
177+
match tauri_utils::io::read_line(&mut stderr, &mut buf) {
178+
Ok(s) if s == 0 => break,
179+
_ => (),
180+
}
181+
debug!(action = "stderr"; "{}", String::from_utf8_lossy(&buf));
182+
lines.extend(buf.clone());
183+
lines.push(b'\n');
184+
}
185+
});
186+
187+
let status = child.wait()?;
188+
let output = Output {
189+
status,
190+
stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),
191+
stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),
192+
};
154193

155194
if output.status.success() {
156195
Ok(output)
157196
} else {
158-
Err(crate::Error::GenericError(
159-
String::from_utf8_lossy(&output.stderr).to_string(),
160-
))
197+
Err(crate::Error::GenericError(format!(
198+
"failed to run {}",
199+
program
200+
)))
161201
}
162202
}
163203
}

tooling/bundler/src/bundle/platform.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub fn target_triple() -> Result<String, crate::Error> {
4141
let arch_res = Command::new("rustc").args(&["--print", "cfg"]).output_ok();
4242

4343
let arch = match arch_res {
44-
Ok(output) => parse_rust_cfg(String::from_utf8_lossy(&output.stdout).into_owned())
44+
Ok(output) => parse_rust_cfg(String::from_utf8_lossy(&output.stdout).into())
4545
.target_arch
4646
.expect("could not find `target_arch` when running `rustc --print cfg`."),
4747
Err(err) => {

tooling/cli/src/build.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,15 @@ pub fn command(options: Options) -> Result<()> {
8181
.arg(before_build)
8282
.current_dir(app_dir())
8383
.envs(command_env(options.debug))
84-
.pipe()?
85-
.status()
84+
.piped()
8685
.with_context(|| format!("failed to run `{}` with `cmd /C`", before_build))?;
8786
#[cfg(not(target_os = "windows"))]
8887
let status = Command::new("sh")
8988
.arg("-c")
9089
.arg(before_build)
9190
.current_dir(app_dir())
9291
.envs(command_env(options.debug))
93-
.pipe()?
94-
.status()
92+
.piped()
9593
.with_context(|| format!("failed to run `{}` with `sh -c`", before_build))?;
9694

9795
if !status.success() {

tooling/cli/src/dev.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
config::{get as get_config, reload as reload_config, AppUrl, ConfigHandle, WindowUrl},
1010
manifest::{rewrite_manifest, Manifest},
1111
},
12-
CommandExt, Result,
12+
Result,
1313
};
1414
use clap::Parser;
1515

@@ -111,8 +111,7 @@ fn command_internal(options: Options) -> Result<()> {
111111
.arg("/C")
112112
.arg(before_dev)
113113
.current_dir(app_dir())
114-
.envs(command_env(true))
115-
.pipe()?; // development build always includes debug information
114+
.envs(command_env(true));
116115
command
117116
};
118117
#[cfg(not(target_os = "windows"))]
@@ -122,11 +121,12 @@ fn command_internal(options: Options) -> Result<()> {
122121
.arg("-c")
123122
.arg(before_dev)
124123
.current_dir(app_dir())
125-
.envs(command_env(true))
126-
.pipe()?; // development build always includes debug information
124+
.envs(command_env(true));
127125
command
128126
};
129127
command.stdin(Stdio::piped());
128+
command.stdout(os_pipe::dup_stdout()?);
129+
command.stderr(os_pipe::dup_stderr()?);
130130

131131
let child = SharedChild::spawn(&mut command)
132132
.unwrap_or_else(|_| panic!("failed to run `{}`", before_dev));

tooling/cli/src/interface/rust.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,19 @@ struct CargoConfig {
101101
}
102102

103103
pub fn build_project(runner: String, args: Vec<String>) -> crate::Result<()> {
104-
Command::new(&runner)
104+
let status = Command::new(&runner)
105105
.args(&["build", "--features=custom-protocol"])
106106
.args(args)
107107
.env("STATIC_VCRUNTIME", "true")
108-
.pipe()?
109-
.output_ok()
110-
.with_context(|| format!("Result of `{} build` operation was unsuccessful", runner))?;
111-
112-
Ok(())
108+
.piped()?;
109+
if status.success() {
110+
Ok(())
111+
} else {
112+
Err(anyhow::anyhow!(
113+
"Result of `{} build` operation was unsuccessful",
114+
runner
115+
))
116+
}
113117
}
114118

115119
pub struct AppSettings {

tooling/cli/src/lib.rs

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use env_logger::Builder;
1919
use log::{debug, log_enabled, Level};
2020
use serde::Deserialize;
2121
use std::ffi::OsString;
22-
use std::io::Write;
23-
use std::process::{Command, Output};
22+
use std::io::{BufReader, Write};
23+
use std::process::{Command, ExitStatus, Stdio};
2424

2525
#[derive(Deserialize)]
2626
pub struct VersionMetadata {
@@ -104,11 +104,16 @@ where
104104
.format_indent(Some(12))
105105
.filter(None, level_from_usize(cli.verbose).to_level_filter())
106106
.format(|f, record| {
107+
let mut is_command_output = false;
107108
if let Some(action) = record.key_values().get("action".into()) {
108-
let mut action_style = f.style();
109-
action_style.set_color(Color::Green).set_bold(true);
110-
111-
write!(f, "{:>12} ", action_style.value(action.to_str().unwrap()))?;
109+
let action = action.to_str().unwrap();
110+
is_command_output = action == "stdout" || action == "stderr";
111+
if !is_command_output {
112+
let mut action_style = f.style();
113+
action_style.set_color(Color::Green).set_bold(true);
114+
115+
write!(f, "{:>12} ", action_style.value(action))?;
116+
}
112117
} else {
113118
let mut level_style = f.default_level_style(record.level());
114119
level_style.set_bold(true);
@@ -120,7 +125,7 @@ where
120125
)?;
121126
}
122127

123-
if log_enabled!(Level::Debug) {
128+
if !is_command_output && log_enabled!(Level::Debug) {
124129
let mut target_style = f.style();
125130
target_style.set_color(Color::Black);
126131

@@ -171,37 +176,61 @@ fn prettyprint_level(lvl: Level) -> &'static str {
171176
pub trait CommandExt {
172177
// The `pipe` function sets the stdout and stderr to properly
173178
// show the command output in the Node.js wrapper.
174-
fn pipe(&mut self) -> Result<&mut Self>;
175-
fn output_ok(&mut self) -> crate::Result<Output>;
179+
fn piped(&mut self) -> Result<ExitStatus>;
180+
fn output_ok(&mut self) -> crate::Result<()>;
176181
}
177182

178183
impl CommandExt for Command {
179-
fn pipe(&mut self) -> Result<&mut Self> {
184+
fn piped(&mut self) -> crate::Result<ExitStatus> {
180185
self.stdout(os_pipe::dup_stdout()?);
181186
self.stderr(os_pipe::dup_stderr()?);
182-
Ok(self)
183-
}
187+
let program = self.get_program().to_string_lossy().into_owned();
188+
debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{} {}", acc, arg)));
184189

185-
fn output_ok(&mut self) -> crate::Result<Output> {
186-
debug!(action = "Running"; "Command `{} {}`", self.get_program().to_string_lossy(), self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{} {}", acc, arg)));
190+
self.status().map_err(Into::into)
191+
}
187192

188-
let output = self.output()?;
193+
fn output_ok(&mut self) -> crate::Result<()> {
194+
let program = self.get_program().to_string_lossy().into_owned();
195+
debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{} {}", acc, arg)));
196+
197+
self.stdout(Stdio::piped());
198+
self.stderr(Stdio::piped());
199+
200+
let mut child = self.spawn()?;
201+
202+
let mut stdout = child.stdout.take().map(BufReader::new).unwrap();
203+
std::thread::spawn(move || {
204+
let mut buf = Vec::new();
205+
loop {
206+
buf.clear();
207+
match tauri_utils::io::read_line(&mut stdout, &mut buf) {
208+
Ok(s) if s == 0 => break,
209+
_ => (),
210+
}
211+
debug!(action = "stdout"; "{}", String::from_utf8_lossy(&buf));
212+
}
213+
});
214+
215+
let mut stderr = child.stderr.take().map(BufReader::new).unwrap();
216+
std::thread::spawn(move || {
217+
let mut buf = Vec::new();
218+
loop {
219+
buf.clear();
220+
match tauri_utils::io::read_line(&mut stderr, &mut buf) {
221+
Ok(s) if s == 0 => break,
222+
_ => (),
223+
}
224+
debug!(action = "stderr"; "{}", String::from_utf8_lossy(&buf));
225+
}
226+
});
189227

190-
let stdout = String::from_utf8_lossy(&output.stdout);
191-
if !stdout.is_empty() {
192-
debug!("Stdout: {}", stdout);
193-
}
194-
let stderr = String::from_utf8_lossy(&output.stderr);
195-
if !stderr.is_empty() {
196-
debug!("Stderr: {}", stderr);
197-
}
228+
let status = child.wait()?;
198229

199-
if output.status.success() {
200-
Ok(output)
230+
if status.success() {
231+
Ok(())
201232
} else {
202-
Err(anyhow::anyhow!(
203-
String::from_utf8_lossy(&output.stderr).to_string()
204-
))
233+
Err(anyhow::anyhow!("failed to run {}", program))
205234
}
206235
}
207236
}

0 commit comments

Comments
 (0)