cli_xtask/
process.rs

1//! Utility functions for working with processes.
2
3use std::process::{Command, Stdio};
4
5use cargo_metadata::Metadata;
6use eyre::eyre;
7
8use crate::{fs::ToRelative, Result};
9
10/// Extension methods for [`std::process::Command`].
11pub trait CommandExt {
12    /// Executes the command as a child process on the workspace root directory,
13    /// waiting for it to finish and checking the exit status.
14    fn workspace_spawn(&mut self, workspace: &Metadata) -> Result<()>;
15
16    /// Executes the command as a child process on the workspace root directory,
17    /// waiting for it to finish and collecting all of its standard output as
18    /// a bytes vector.
19    fn workspace_stdout_raw(&mut self, workspace: &Metadata) -> Result<Vec<u8>>;
20
21    /// Executes the command as a child process on the workspace root directory,
22    /// waiting for it to finish and collecting all of its standard output as
23    /// a string.
24    fn workspace_stdout(&mut self, workspace: &Metadata) -> Result<String>;
25}
26
27impl CommandExt for Command {
28    fn workspace_spawn(&mut self, workspace: &Metadata) -> Result<()> {
29        let workspace_root = &workspace.workspace_root;
30
31        self.current_dir(workspace_root);
32
33        let program = self.get_program();
34        let args = self.get_args();
35        tracing::info!(
36            "[{}]$ {}{}",
37            workspace.workspace_root.to_relative(),
38            program.to_string_lossy(),
39            args.fold(String::new(), |mut s, a| {
40                s.push(' ');
41                s.push_str(a.to_string_lossy().as_ref());
42                s
43            })
44        );
45
46        let status = self.status()?;
47        if !status.success() {
48            tracing::error!(
49                "Command for {} failed with status {}",
50                workspace_root.to_relative(),
51                status,
52            );
53            return Err(eyre!(
54                "command for {} failed with status {}",
55                workspace_root.to_relative(),
56                status,
57            ));
58        }
59        Ok(())
60    }
61
62    fn workspace_stdout_raw(&mut self, workspace: &Metadata) -> Result<Vec<u8>> {
63        let workspace_root = &workspace.workspace_root;
64
65        self.current_dir(workspace_root).stdout(Stdio::piped());
66
67        let program = self.get_program();
68        let args = self.get_args();
69        tracing::info!(
70            "[{}]$ {}{}",
71            workspace.workspace_root.to_relative(),
72            program.to_string_lossy(),
73            args.fold(String::new(), |mut s, a| {
74                s.push(' ');
75                s.push_str(a.to_string_lossy().as_ref());
76                s
77            })
78        );
79
80        let output = self.spawn()?.wait_with_output()?;
81
82        if !output.status.success() {
83            tracing::error!(
84                "Command for {} failed with status {}",
85                workspace_root.to_relative(),
86                output.status.code().unwrap()
87            );
88            return Err(eyre!(
89                "command for {} failed with status {}",
90                workspace_root.to_relative(),
91                output.status.code().unwrap()
92            ));
93        }
94
95        Ok(output.stdout)
96    }
97
98    fn workspace_stdout(&mut self, workspace: &Metadata) -> Result<String> {
99        let output = self.workspace_stdout_raw(workspace)?;
100        Ok(String::from_utf8(output)?)
101    }
102}