@@ -67,12 +67,23 @@ use std::env;
6767use std:: error;
6868use std:: ffi:: { OsStr , OsString } ;
6969use std:: fmt;
70+ use std:: fmt:: Display ;
7071use std:: io;
7172use std:: ops:: { Bound , RangeBounds } ;
7273use std:: path:: PathBuf ;
7374use std:: process:: { Command , Output } ;
7475use 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 ) ]
7788pub 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+
151237impl error:: Error for Error { }
152238
153239impl 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: {}\n error: 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, "\n HINT: {}" , 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