Skip to content

Commit 9e485f0

Browse files
authored
Merge pull request #158 from Finchiedev/master
Improve error message when a library is not found
2 parents ef638e8 + 08a1c5f commit 9e485f0

File tree

1 file changed

+162
-8
lines changed

1 file changed

+162
-8
lines changed

src/lib.rs

Lines changed: 162 additions & 8 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 {
@@ -208,12 +294,80 @@ impl fmt::Display for Error {
208294
ref command,
209295
ref output,
210296
} => {
211-
write!(
297+
let crate_name =
298+
env::var("CARGO_PKG_NAME").unwrap_or(String::from("<NO CRATE NAME>"));
299+
300+
writeln!(f, "")?;
301+
302+
// Give a short explanation of what the error is
303+
writeln!(
212304
f,
213-
"`{}` did not exit successfully: {}\nerror: could not find system library '{}' required by the '{}' crate\n",
214-
command, output.status, name, env::var("CARGO_PKG_NAME").unwrap_or_default(),
305+
"pkg-config {}",
306+
match output.status.code() {
307+
Some(code) => format!("exited with status code {}", code),
308+
None => "was terminated by signal".to_string(),
309+
}
215310
)?;
216-
format_output(output, f)
311+
312+
// Give the command run so users can reproduce the error
313+
writeln!(f, "> {}\n", command)?;
314+
315+
// Explain how it was caused
316+
writeln!(
317+
f,
318+
"The system library `{}` required by crate `{}` was not found.",
319+
name, crate_name
320+
)?;
321+
writeln!(
322+
f,
323+
"The file `{}.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.",
324+
name
325+
)?;
326+
327+
// There will be no status code if terminated by signal
328+
if let Some(_code) = output.status.code() {
329+
// Nix uses a wrapper script for pkg-config that sets the custom
330+
// environment variable PKG_CONFIG_PATH_FOR_TARGET
331+
let search_locations = ["PKG_CONFIG_PATH_FOR_TARGET", "PKG_CONFIG_PATH"];
332+
333+
// Find a search path to use
334+
let mut search_data = None;
335+
for location in search_locations.iter() {
336+
if let Ok(search_path) = env::var(location) {
337+
search_data = Some((location, search_path));
338+
break;
339+
}
340+
}
341+
342+
// Guess the most reasonable course of action
343+
let hint = if let Some((search_location, search_path)) = search_data {
344+
writeln!(
345+
f,
346+
"{} contains the following:\n{}",
347+
search_location,
348+
search_path
349+
.split(':')
350+
.map(|path| format!(" - {}", path))
351+
.collect::<Vec<String>>()
352+
.join("\n"),
353+
)?;
354+
355+
format!("you may need to install a package such as {name}, {name}-dev or {name}-devel.", name=name)
356+
} else {
357+
// Even on Nix, setting PKG_CONFIG_PATH seems to be a viable option
358+
writeln!(f, "The PKG_CONFIG_PATH environment variable is not set.")?;
359+
360+
format!(
361+
"if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing `{}.pc`.",
362+
name
363+
)
364+
};
365+
366+
// Try and nudge the user in the right direction so they don't get stuck
367+
writeln!(f, "\nHINT: {}", hint)?;
368+
}
369+
370+
Ok(())
217371
}
218372
Error::Failure {
219373
ref command,
@@ -499,20 +653,20 @@ impl Config {
499653
Ok(output.stdout)
500654
} else {
501655
Err(Error::Failure {
502-
command: format!("{:?}", cmd),
656+
command: format!("{}", cmd),
503657
output,
504658
})
505659
}
506660
}
507661
Err(cause) => Err(Error::Command {
508-
command: format!("{:?}", cmd),
662+
command: format!("{}", cmd),
509663
cause,
510664
}),
511665
}
512666
}
513667

514-
fn command(&self, exe: OsString, name: &str, args: &[&str]) -> Command {
515-
let mut cmd = Command::new(exe);
668+
fn command(&self, exe: OsString, name: &str, args: &[&str]) -> WrappedCommand {
669+
let mut cmd = WrappedCommand::new(exe);
516670
if self.is_static(name) {
517671
cmd.arg("--static");
518672
}

0 commit comments

Comments
 (0)