Skip to content

Commit 98114b3

Browse files
committed
Append command location information into errors
1 parent 0755742 commit 98114b3

File tree

4 files changed

+98
-31
lines changed

4 files changed

+98
-31
lines changed

macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ pub fn spawn_with_output(input: proc_macro::TokenStream) -> proc_macro::TokenStr
207207
pub fn cmd_die(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
208208
let msg = parse_msg(input.into());
209209
quote!({
210-
::cmd_lib::error!("FATAL: {}", #msg);
210+
::cmd_lib::error!("FATAL: {} at {}:{}", #msg, file!(), line!());
211211
std::process::exit(1)
212212
})
213213
.into()

macros/src/parser.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ impl<I: Iterator<Item = ParseArg>> Parser<I> {
5151
}
5252

5353
fn parse_pipe(&mut self) -> TokenStream {
54-
let mut ret = quote!(::cmd_lib::Cmd::default());
54+
// TODO: get accurate line number once `proc_macro::Span::line()` API is stable
55+
let mut ret = quote!(::cmd_lib::Cmd::default().with_location(file!(), line!()));
5556
while let Some(arg) = self.iter.peek() {
5657
match arg {
5758
ParseArg::RedirectFd(fd1, fd2) => {

src/child.rs

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ impl FunChildren {
103103
/// provided function.
104104
pub fn wait_with_pipe(&mut self, f: &mut dyn FnMut(Box<dyn Read>)) -> CmdResult {
105105
let child = self.children.pop().unwrap();
106-
let stderr_thread = StderrThread::new(&child.cmd, child.stderr, false);
106+
let stderr_thread =
107+
StderrThread::new(&child.cmd, &child.file, child.line, child.stderr, false);
107108
match child.handle {
108109
CmdChildHandle::Proc(mut proc) => {
109110
if let Some(stdout) = child.stdout {
@@ -145,6 +146,8 @@ impl FunChildren {
145146
pub(crate) struct CmdChild {
146147
handle: CmdChildHandle,
147148
cmd: String,
149+
file: String,
150+
line: u32,
148151
stdout: Option<PipeReader>,
149152
stderr: Option<PipeReader>,
150153
}
@@ -153,10 +156,14 @@ impl CmdChild {
153156
pub(crate) fn new(
154157
handle: CmdChildHandle,
155158
cmd: String,
159+
file: String,
160+
line: u32,
156161
stdout: Option<PipeReader>,
157162
stderr: Option<PipeReader>,
158163
) -> Self {
159164
Self {
165+
file,
166+
line,
160167
handle,
161168
cmd,
162169
stdout,
@@ -165,8 +172,9 @@ impl CmdChild {
165172
}
166173

167174
fn wait(mut self, is_last: bool) -> CmdResult {
168-
let _stderr_thread = StderrThread::new(&self.cmd, self.stderr.take(), false);
169-
let res = self.handle.wait(&self.cmd);
175+
let _stderr_thread =
176+
StderrThread::new(&self.cmd, &self.file, self.line, self.stderr.take(), false);
177+
let res = self.handle.wait(&self.cmd, &self.file, self.line);
170178
if let Err(e) = res {
171179
if is_last || process::pipefail_enabled() {
172180
return Err(e);
@@ -184,7 +192,13 @@ impl CmdChild {
184192
}
185193

186194
fn wait_with_all(mut self, capture: bool) -> (CmdResult, FunResult, FunResult) {
187-
let mut stderr_thread = StderrThread::new(&self.cmd, self.stderr.take(), capture);
195+
let mut stderr_thread = StderrThread::new(
196+
&self.cmd,
197+
&self.file,
198+
self.line,
199+
self.stderr.take(),
200+
capture,
201+
);
188202
let stdout_output = {
189203
if let Some(mut out) = self.stdout.take() {
190204
let mut s = String::new();
@@ -202,7 +216,7 @@ impl CmdChild {
202216
}
203217
};
204218
let stderr_output = stderr_thread.join();
205-
let res = self.handle.wait(&self.cmd);
219+
let res = self.handle.wait(&self.cmd, &self.file, self.line);
206220
(res, stdout_output, stderr_output)
207221
}
208222

@@ -222,17 +236,19 @@ pub(crate) enum CmdChildHandle {
222236
}
223237

224238
impl CmdChildHandle {
225-
fn wait(self, cmd: &str) -> CmdResult {
239+
fn wait(self, cmd: &str, file: &str, line: u32) -> CmdResult {
226240
match self {
227241
CmdChildHandle::Proc(mut proc) => {
228242
let status = proc.wait();
229243
match status {
230-
Err(e) => return Err(process::new_cmd_io_error(&e, cmd)),
244+
Err(e) => return Err(process::new_cmd_io_error(&e, cmd, file, line)),
231245
Ok(status) => {
232246
if !status.success() {
233247
return Err(Self::status_to_io_error(
234248
status,
235249
&format!("Running [{cmd}] exited with error"),
250+
file,
251+
line,
236252
));
237253
}
238254
}
@@ -243,13 +259,15 @@ impl CmdChildHandle {
243259
match status {
244260
Ok(result) => {
245261
if let Err(e) = result {
246-
return Err(process::new_cmd_io_error(&e, cmd));
262+
return Err(process::new_cmd_io_error(&e, cmd, file, line));
247263
}
248264
}
249265
Err(e) => {
250266
return Err(Error::new(
251267
ErrorKind::Other,
252-
format!("Running [{cmd}] thread joined with error: {e:?}"),
268+
format!(
269+
"Running [{cmd}] thread joined with error: {e:?} at {file}:{line}"
270+
),
253271
))
254272
}
255273
}
@@ -259,11 +277,17 @@ impl CmdChildHandle {
259277
Ok(())
260278
}
261279

262-
fn status_to_io_error(status: ExitStatus, cmd: &str) -> Error {
280+
fn status_to_io_error(status: ExitStatus, run_cmd: &str, file: &str, line: u32) -> Error {
263281
if let Some(code) = status.code() {
264-
Error::new(ErrorKind::Other, format!("{cmd}; status code: {code}"))
282+
Error::new(
283+
ErrorKind::Other,
284+
format!("{run_cmd}; status code: {code} at {file}:{line}"),
285+
)
265286
} else {
266-
Error::new(ErrorKind::Other, format!("{cmd}; terminated by {status}"))
287+
Error::new(
288+
ErrorKind::Other,
289+
format!("{run_cmd}; terminated by {status} at {file}:{line}"),
290+
)
267291
}
268292
}
269293

@@ -288,10 +312,12 @@ impl CmdChildHandle {
288312
struct StderrThread {
289313
thread: Option<JoinHandle<String>>,
290314
cmd: String,
315+
file: String,
316+
line: u32,
291317
}
292318

293319
impl StderrThread {
294-
fn new(cmd: &str, stderr: Option<PipeReader>, capture: bool) -> Self {
320+
fn new(cmd: &str, file: &str, line: u32, stderr: Option<PipeReader>, capture: bool) -> Self {
295321
if let Some(stderr) = stderr {
296322
let thread = std::thread::spawn(move || {
297323
let mut output = String::new();
@@ -312,11 +338,15 @@ impl StderrThread {
312338
});
313339
Self {
314340
cmd: cmd.into(),
341+
file: file.into(),
342+
line,
315343
thread: Some(thread),
316344
}
317345
} else {
318346
Self {
319347
cmd: cmd.into(),
348+
file: file.into(),
349+
line,
320350
thread: None,
321351
}
322352
}
@@ -326,10 +356,12 @@ impl StderrThread {
326356
if let Some(thread) = self.thread.take() {
327357
match thread.join() {
328358
Err(e) => {
329-
let cmd = &self.cmd;
330359
return Err(Error::new(
331360
ErrorKind::Other,
332-
format!("Running [{cmd}] stderr thread joined with error: {e:?}",),
361+
format!(
362+
"Running [{}] stderr thread joined with error: {:?} at {}:{}",
363+
self.cmd, e, self.file, self.line
364+
),
333365
));
334366
}
335367
Ok(output) => return Ok(output),

src/process.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,16 @@ pub struct Cmds {
163163
cmds: Vec<Option<Cmd>>,
164164
full_cmds: String,
165165
ignore_error: bool,
166+
file: String,
167+
line: u32,
166168
}
167169

168170
impl Cmds {
169171
pub fn pipe(mut self, cmd: Cmd) -> Self {
170-
if !self.full_cmds.is_empty() {
172+
if self.full_cmds.is_empty() {
173+
self.file = cmd.file.clone();
174+
self.line = cmd.line;
175+
} else {
171176
self.full_cmds += " | ";
172177
}
173178
self.full_cmds += &cmd.cmd_str();
@@ -177,17 +182,22 @@ impl Cmds {
177182
// first command in the pipe
178183
self.ignore_error = true;
179184
} else {
180-
warn!("Builtin {IGNORE_CMD:?} command at wrong position");
185+
warn!(
186+
"Builtin {IGNORE_CMD:?} command at wrong position ({}:{})",
187+
self.file, self.line
188+
);
181189
}
182190
}
183191
self.cmds.push(Some(cmd));
184192
self
185193
}
186194

187195
fn spawn(&mut self, current_dir: &mut PathBuf, with_output: bool) -> Result<CmdChildren> {
188-
let full_cmds = format!("[{}]", self.full_cmds);
196+
let full_cmds = self.full_cmds.clone();
197+
let file = self.file.clone();
198+
let line = self.line;
189199
if debug_enabled() {
190-
debug!("Running {full_cmds} ...");
200+
debug!("Running [{full_cmds}] at {file}:{line} ...");
191201
}
192202

193203
// spawning all the sub-processes
@@ -199,17 +209,17 @@ impl Cmds {
199209
if i != len - 1 {
200210
// not the last, update redirects
201211
let (pipe_reader, pipe_writer) =
202-
os_pipe::pipe().map_err(|e| new_cmd_io_error(&e, &full_cmds))?;
212+
os_pipe::pipe().map_err(|e| new_cmd_io_error(&e, &full_cmds, &file, line))?;
203213
cmd.setup_redirects(&mut prev_pipe_in, Some(pipe_writer), with_output)
204-
.map_err(|e| new_cmd_io_error(&e, &full_cmds))?;
214+
.map_err(|e| new_cmd_io_error(&e, &full_cmds, &file, line))?;
205215
prev_pipe_in = Some(pipe_reader);
206216
} else {
207217
cmd.setup_redirects(&mut prev_pipe_in, None, with_output)
208-
.map_err(|e| new_cmd_io_error(&e, &full_cmds))?;
218+
.map_err(|e| new_cmd_io_error(&e, &full_cmds, &file, line))?;
209219
}
210220
let child = cmd
211221
.spawn(current_dir, with_output)
212-
.map_err(|e| new_cmd_io_error(&e, &full_cmds))?;
222+
.map_err(|e| new_cmd_io_error(&e, &full_cmds, &file, line))?;
213223
children.push(child);
214224
}
215225

@@ -269,6 +279,8 @@ pub struct Cmd {
269279
args: Vec<OsString>,
270280
vars: HashMap<String, String>,
271281
redirects: Vec<Redirect>,
282+
file: String,
283+
line: u32,
272284

273285
// for running
274286
std_cmd: Option<Command>,
@@ -286,6 +298,8 @@ impl Default for Cmd {
286298
args: vec![],
287299
vars: HashMap::new(),
288300
redirects: vec![],
301+
file: "".into(),
302+
line: 0,
289303
std_cmd: None,
290304
stdin_redirect: None,
291305
stdout_redirect: None,
@@ -297,6 +311,12 @@ impl Default for Cmd {
297311
}
298312

299313
impl Cmd {
314+
pub fn with_location(mut self, file: &str, line: u32) -> Self {
315+
self.file = file.into();
316+
self.line = line;
317+
self
318+
}
319+
300320
pub fn add_arg<O>(mut self, arg: O) -> Self
301321
where
302322
O: AsRef<OsStr>,
@@ -369,10 +389,12 @@ impl Cmd {
369389
fn spawn(mut self, current_dir: &mut PathBuf, with_output: bool) -> Result<CmdChild> {
370390
let arg0 = self.arg0();
371391
if arg0 == CD_CMD {
372-
self.run_cd_cmd(current_dir)?;
392+
self.run_cd_cmd(current_dir, &self.file, self.line)?;
373393
Ok(CmdChild::new(
374394
CmdChildHandle::SyncFn,
375395
self.cmd_str(),
396+
self.file,
397+
self.line,
376398
self.stdout_logging,
377399
self.stderr_logging,
378400
))
@@ -415,6 +437,8 @@ impl Cmd {
415437
Ok(CmdChild::new(
416438
CmdChildHandle::Thread(handle),
417439
cmd_str,
440+
self.file,
441+
self.line,
418442
self.stdout_logging,
419443
self.stderr_logging,
420444
))
@@ -423,6 +447,8 @@ impl Cmd {
423447
Ok(CmdChild::new(
424448
CmdChildHandle::SyncFn,
425449
cmd_str,
450+
self.file,
451+
self.line,
426452
self.stdout_logging,
427453
self.stderr_logging,
428454
))
@@ -455,23 +481,28 @@ impl Cmd {
455481
Ok(CmdChild::new(
456482
CmdChildHandle::Proc(child),
457483
self.cmd_str(),
484+
self.file,
485+
self.line,
458486
self.stdout_logging,
459487
self.stderr_logging,
460488
))
461489
}
462490
}
463491

464-
fn run_cd_cmd(&self, current_dir: &mut PathBuf) -> CmdResult {
492+
fn run_cd_cmd(&self, current_dir: &mut PathBuf, file: &str, line: u32) -> CmdResult {
465493
if self.args.len() == 1 {
466-
return Err(Error::new(ErrorKind::Other, "{CD_CMD}: missing directory"));
494+
return Err(Error::new(
495+
ErrorKind::Other,
496+
"{CD_CMD}: missing directory at {file}:{line}",
497+
));
467498
} else if self.args.len() > 2 {
468499
let err_msg = format!("{CD_CMD}: too many arguments");
469500
return Err(Error::new(ErrorKind::Other, err_msg));
470501
}
471502

472503
let dir = current_dir.join(&self.args[1]);
473504
if !dir.is_dir() {
474-
let err_msg = format!("{CD_CMD}: No such file or directory");
505+
let err_msg = format!("{CD_CMD}: No such file or directory at {file}:{line}");
475506
return Err(Error::new(ErrorKind::Other, err_msg));
476507
}
477508

@@ -606,8 +637,11 @@ impl fmt::Display for CmdString {
606637
}
607638
}
608639

609-
pub(crate) fn new_cmd_io_error(e: &Error, command: &str) -> Error {
610-
Error::new(e.kind(), format!("Running {command} failed: {e}"))
640+
pub(crate) fn new_cmd_io_error(e: &Error, command: &str, file: &str, line: u32) -> Error {
641+
Error::new(
642+
e.kind(),
643+
format!("Running [{command}] failed: {e} at {file}:{line}"),
644+
)
611645
}
612646

613647
#[cfg(test)]

0 commit comments

Comments
 (0)