Skip to content

Commit a0640aa

Browse files
committed
Wrap std::process::Command to track arguments and environment variables
Before, this was accomplished by using `get_env` and `get_args` manually, but these features are unavailable in 1.30 (current MSRV). To get around this, `Command` is wrapped as `WrappedCommand` and tracks any environment variables or arguments that are set by the caller. Then, the `Display` implementation actually handles pretty-printing the invocation, with the added benefit that other errors can take advantage of this nicer output!
1 parent d5be171 commit a0640aa

File tree

1 file changed

+90
-30
lines changed

1 file changed

+90
-30
lines changed

src/lib.rs

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,23 @@ use std::env;
6767
use std::error;
6868
use std::ffi::{OsStr, OsString};
6969
use std::fmt;
70+
use std::fmt::Display;
7071
use std::io;
7172
use std::ops::{Bound, RangeBounds};
7273
use std::path::PathBuf;
7374
use std::process::{Command, Output};
7475
use std::str;
7576

77+
/// Wrapper struct to polyfill methods introduced in 1.57 (`get_envs`, `get_args` etc).
78+
/// This is needed to reconstruct the pkg-config command for output in a copy-
79+
/// paste friendly format via `Display`.
80+
struct WrappedCommand {
81+
inner: Command,
82+
program: OsString,
83+
env_vars: Vec<(OsString, OsString)>,
84+
args: Vec<OsString>,
85+
}
86+
7687
#[derive(Clone, Debug)]
7788
pub struct Config {
7889
statik: Option<bool>,
@@ -148,6 +159,81 @@ pub enum Error {
148159
__Nonexhaustive,
149160
}
150161

162+
impl WrappedCommand {
163+
fn new<S: AsRef<OsStr>>(program: S) -> Self {
164+
Self {
165+
inner: Command::new(program.as_ref()),
166+
program: program.as_ref().to_os_string(),
167+
env_vars: Vec::new(),
168+
args: Vec::new(),
169+
}
170+
}
171+
172+
fn args<I, S>(&mut self, args: I) -> &mut Self
173+
where
174+
I: IntoIterator<Item = S> + Clone,
175+
S: AsRef<OsStr>,
176+
{
177+
self.inner.args(args.clone());
178+
self.args
179+
.extend(args.into_iter().map(|arg| arg.as_ref().to_os_string()));
180+
181+
self
182+
}
183+
184+
fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
185+
self.inner.arg(arg.as_ref());
186+
self.args.push(arg.as_ref().to_os_string());
187+
188+
self
189+
}
190+
191+
fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
192+
where
193+
K: AsRef<OsStr>,
194+
V: AsRef<OsStr>,
195+
{
196+
self.inner.env(key.as_ref(), value.as_ref());
197+
self.env_vars
198+
.push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
199+
200+
self
201+
}
202+
203+
fn output(&mut self) -> io::Result<Output> {
204+
self.inner.output()
205+
}
206+
}
207+
208+
/// Output a command invocation that can be copy-pasted into the terminal.
209+
/// `Command`'s existing debug implementation is not used for that reason,
210+
/// as it can sometimes lead to output such as:
211+
/// `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS="1" PKG_CONFIG_ALLOW_SYSTEM_LIBS="1" "pkg-config" "--libs" "--cflags" "mylibrary"`
212+
/// Which cannot be copy-pasted into terminals such as nushell, and is a bit noisy.
213+
/// This will look something like:
214+
/// `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs --cflags mylibrary`
215+
impl Display for WrappedCommand {
216+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217+
// Format all explicitly defined environment variables
218+
let envs = self
219+
.env_vars
220+
.iter()
221+
.map(|(env, arg)| format!("{}={}", env.to_string_lossy(), arg.to_string_lossy()))
222+
.collect::<Vec<String>>()
223+
.join(" ");
224+
225+
// Format all pkg-config arguments
226+
let args = self
227+
.args
228+
.iter()
229+
.map(|arg| arg.to_string_lossy().to_string())
230+
.collect::<Vec<String>>()
231+
.join(" ");
232+
233+
write!(f, "{} {} {}", envs, self.program.to_string_lossy(), args)
234+
}
235+
}
236+
151237
impl error::Error for Error {}
152238

153239
impl fmt::Debug for Error {
@@ -558,47 +644,21 @@ impl Config {
558644
if output.status.success() {
559645
Ok(output.stdout)
560646
} else {
561-
// Collect all explicitly-defined environment variables
562-
// this is used to display the equivalent pkg-config shell invocation
563-
let envs = cmd
564-
.get_envs()
565-
.map(|(env, optional_arg)| {
566-
if let Some(arg) = optional_arg {
567-
format!("{}={}", env.to_string_lossy(), arg.to_string_lossy())
568-
} else {
569-
env.to_string_lossy().to_string()
570-
}
571-
})
572-
.collect::<Vec<String>>();
573-
574-
// Collect arguments for the same reason
575-
let args = cmd
576-
.get_args()
577-
.map(|arg| arg.to_string_lossy().to_string())
578-
.collect::<Vec<String>>();
579-
580-
// This will look something like:
581-
// PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs --cflags {library}
582647
Err(Error::Failure {
583-
command: format!(
584-
"{} {} {}",
585-
envs.join(" "),
586-
cmd.get_program().to_string_lossy(),
587-
args.join(" ")
588-
),
648+
command: format!("{}", cmd),
589649
output,
590650
})
591651
}
592652
}
593653
Err(cause) => Err(Error::Command {
594-
command: format!("{:?}", cmd),
654+
command: format!("{}", cmd),
595655
cause,
596656
}),
597657
}
598658
}
599659

600-
fn command(&self, exe: OsString, name: &str, args: &[&str]) -> Command {
601-
let mut cmd = Command::new(exe);
660+
fn command(&self, exe: OsString, name: &str, args: &[&str]) -> WrappedCommand {
661+
let mut cmd = WrappedCommand::new(exe);
602662
if self.is_static(name) {
603663
cmd.arg("--static");
604664
}

0 commit comments

Comments
 (0)