@@ -67,12 +67,23 @@ use std::env;
67
67
use std:: error;
68
68
use std:: ffi:: { OsStr , OsString } ;
69
69
use std:: fmt;
70
+ use std:: fmt:: Display ;
70
71
use std:: io;
71
72
use std:: ops:: { Bound , RangeBounds } ;
72
73
use std:: path:: PathBuf ;
73
74
use std:: process:: { Command , Output } ;
74
75
use std:: str;
75
76
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
+
76
87
#[ derive( Clone , Debug ) ]
77
88
pub struct Config {
78
89
statik : Option < bool > ,
@@ -148,6 +159,81 @@ pub enum Error {
148
159
__Nonexhaustive,
149
160
}
150
161
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
+
151
237
impl error:: Error for Error { }
152
238
153
239
impl fmt:: Debug for Error {
@@ -208,12 +294,80 @@ impl fmt::Display for Error {
208
294
ref command,
209
295
ref output,
210
296
} => {
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 ! (
212
304
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
+ }
215
310
) ?;
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 ( ( ) )
217
371
}
218
372
Error :: Failure {
219
373
ref command,
@@ -499,20 +653,20 @@ impl Config {
499
653
Ok ( output. stdout )
500
654
} else {
501
655
Err ( Error :: Failure {
502
- command : format ! ( "{:? }" , cmd) ,
656
+ command : format ! ( "{}" , cmd) ,
503
657
output,
504
658
} )
505
659
}
506
660
}
507
661
Err ( cause) => Err ( Error :: Command {
508
- command : format ! ( "{:? }" , cmd) ,
662
+ command : format ! ( "{}" , cmd) ,
509
663
cause,
510
664
} ) ,
511
665
}
512
666
}
513
667
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) ;
516
670
if self . is_static ( name) {
517
671
cmd. arg ( "--static" ) ;
518
672
}
0 commit comments