1 //! A build dependency for Cargo libraries to find system artifacts through the
2 //! `pkg-config` utility.
3 //!
4 //! This library will shell out to `pkg-config` as part of build scripts and
5 //! probe the system to determine how to link to a specified library. The
6 //! `Config` structure serves as a method of configuring how `pkg-config` is
7 //! invoked in a builder style.
8 //!
9 //! A number of environment variables are available to globally configure how
10 //! this crate will invoke `pkg-config`:
11 //!
12 //! * `FOO_NO_PKG_CONFIG` - if set, this will disable running `pkg-config` when
13 //!   probing for the library named `foo`.
14 //!
15 //! * `PKG_CONFIG_ALLOW_CROSS` - The `pkg-config` command usually doesn't
16 //!   support cross-compilation, and this crate prevents it from selecting
17 //!   incompatible versions of libraries.
18 //!   Setting `PKG_CONFIG_ALLOW_CROSS=1` disables this protection, which is
19 //!   likely to cause linking errors, unless `pkg-config` has been configured
20 //!   to use appropriate sysroot and search paths for the target platform.
21 //!
22 //! There are also a number of environment variables which can configure how a
23 //! library is linked to (dynamically vs statically). These variables control
24 //! whether the `--static` flag is passed. Note that this behavior can be
25 //! overridden by configuring explicitly on `Config`. The variables are checked
26 //! in the following order:
27 //!
28 //! * `FOO_STATIC` - pass `--static` for the library `foo`
29 //! * `FOO_DYNAMIC` - do not pass `--static` for the library `foo`
30 //! * `PKG_CONFIG_ALL_STATIC` - pass `--static` for all libraries
31 //! * `PKG_CONFIG_ALL_DYNAMIC` - do not pass `--static` for all libraries
32 //!
33 //! After running `pkg-config` all appropriate Cargo metadata will be printed on
34 //! stdout if the search was successful.
35 //!
36 //! # Example
37 //!
38 //! Find the system library named `foo`, with minimum version 1.2.3:
39 //!
40 //! ```no_run
41 //! fn main() {
42 //!     pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
43 //! }
44 //! ```
45 //!
46 //! Find the system library named `foo`, with no version requirement (not
47 //! recommended):
48 //!
49 //! ```no_run
50 //! fn main() {
51 //!     pkg_config::probe_library("foo").unwrap();
52 //! }
53 //! ```
54 //!
55 //! Configure how library `foo` is linked to.
56 //!
57 //! ```no_run
58 //! fn main() {
59 //!     pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
60 //! }
61 //! ```
62 
63 #![doc(html_root_url = "https://docs.rs/pkg-config/0.3")]
64 
65 use std::collections::HashMap;
66 use std::env;
67 use std::error;
68 use std::ffi::{OsStr, OsString};
69 use std::fmt;
70 use std::io;
71 use std::ops::{Bound, RangeBounds};
72 use std::path::PathBuf;
73 use std::process::{Command, Output};
74 use std::str;
75 
76 #[derive(Clone, Debug)]
77 pub struct Config {
78     statik: Option<bool>,
79     min_version: Bound<String>,
80     max_version: Bound<String>,
81     extra_args: Vec<OsString>,
82     cargo_metadata: bool,
83     env_metadata: bool,
84     print_system_libs: bool,
85     print_system_cflags: bool,
86 }
87 
88 #[derive(Clone, Debug)]
89 pub struct Library {
90     /// Libraries specified by -l
91     pub libs: Vec<String>,
92     /// Library search paths specified by -L
93     pub link_paths: Vec<PathBuf>,
94     /// Library file paths specified without -l
95     pub link_files: Vec<PathBuf>,
96     /// Darwin frameworks specified by -framework
97     pub frameworks: Vec<String>,
98     /// Darwin framework search paths specified by -F
99     pub framework_paths: Vec<PathBuf>,
100     /// C/C++ header include paths specified by -I
101     pub include_paths: Vec<PathBuf>,
102     /// Linker options specified by -Wl
103     pub ld_args: Vec<Vec<String>>,
104     /// C/C++ definitions specified by -D
105     pub defines: HashMap<String, Option<String>>,
106     /// Version specified by .pc file's Version field
107     pub version: String,
108     /// Ensure that this struct can only be created via its private `[Library::new]` constructor.
109     /// Users of this crate can only access the struct via `[Config::probe]`.
110     _priv: (),
111 }
112 
113 /// Represents all reasons `pkg-config` might not succeed or be run at all.
114 pub enum Error {
115     /// Aborted because of `*_NO_PKG_CONFIG` environment variable.
116     ///
117     /// Contains the name of the responsible environment variable.
118     EnvNoPkgConfig(String),
119 
120     /// Detected cross compilation without a custom sysroot.
121     ///
122     /// Ignore the error with `PKG_CONFIG_ALLOW_CROSS=1`,
123     /// which may let `pkg-config` select libraries
124     /// for the host's architecture instead of the target's.
125     CrossCompilation,
126 
127     /// Failed to run `pkg-config`.
128     ///
129     /// Contains the command and the cause.
130     Command { command: String, cause: io::Error },
131 
132     /// `pkg-config` did not exit successfully after probing a library.
133     ///
134     /// Contains the command and output.
135     Failure { command: String, output: Output },
136 
137     /// `pkg-config` did not exit successfully on the first attempt to probe a library.
138     ///
139     /// Contains the command and output.
140     ProbeFailure {
141         name: String,
142         command: String,
143         output: Output,
144     },
145 
146     #[doc(hidden)]
147     // please don't match on this, we're likely to add more variants over time
148     __Nonexhaustive,
149 }
150 
151 impl error::Error for Error {}
152 
153 impl fmt::Debug for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>154     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
155         // Failed `unwrap()` prints Debug representation, but the default debug format lacks helpful instructions for the end users
156         <Error as fmt::Display>::fmt(self, f)
157     }
158 }
159 
160 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>161     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
162         match *self {
163             Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name),
164             Error::CrossCompilation => f.write_str(
165                 "pkg-config has not been configured to support cross-compilation.\n\
166                 \n\
167                 Install a sysroot for the target platform and configure it via\n\
168                 PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a\n\
169                 cross-compiling wrapper for pkg-config and set it via\n\
170                 PKG_CONFIG environment variable.",
171             ),
172             Error::Command {
173                 ref command,
174                 ref cause,
175             } => {
176                 match cause.kind() {
177                     io::ErrorKind::NotFound => {
178                         let crate_name =
179                             std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "sys".to_owned());
180                         let instructions = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
181                             "Try `brew install pkg-config` if you have Homebrew.\n"
182                         } else if cfg!(unix) {
183                             "Try `apt install pkg-config`, or `yum install pkg-config`,\n\
184                             or `pkg install pkg-config`, or `apk add pkgconfig` \
185                             depending on your distribution.\n"
186                         } else {
187                             "" // There's no easy fix for Windows users
188                         };
189                         write!(f, "Could not run `{command}`\n\
190                         The pkg-config command could not be found.\n\
191                         \n\
192                         Most likely, you need to install a pkg-config package for your OS.\n\
193                         {instructions}\
194                         \n\
195                         If you've already installed it, ensure the pkg-config command is one of the\n\
196                         directories in the PATH environment variable.\n\
197                         \n\
198                         If you did not expect this build to link to a pre-installed system library,\n\
199                         then check documentation of the {crate_name} crate for an option to\n\
200                         build the library from source, or disable features or dependencies\n\
201                         that require pkg-config.", command = command, instructions = instructions, crate_name = crate_name)
202                     }
203                     _ => write!(f, "Failed to run command `{}`, because: {}", command, cause),
204                 }
205             }
206             Error::ProbeFailure {
207                 ref name,
208                 ref command,
209                 ref output,
210             } => {
211                 write!(
212                     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(),
215                 )?;
216                 format_output(output, f)
217             }
218             Error::Failure {
219                 ref command,
220                 ref output,
221             } => {
222                 write!(
223                     f,
224                     "`{}` did not exit successfully: {}",
225                     command, output.status
226                 )?;
227                 format_output(output, f)
228             }
229             Error::__Nonexhaustive => panic!(),
230         }
231     }
232 }
233 
format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result234 fn format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result {
235     let stdout = String::from_utf8_lossy(&output.stdout);
236     if !stdout.is_empty() {
237         write!(f, "\n--- stdout\n{}", stdout)?;
238     }
239     let stderr = String::from_utf8_lossy(&output.stderr);
240     if !stderr.is_empty() {
241         write!(f, "\n--- stderr\n{}", stderr)?;
242     }
243     Ok(())
244 }
245 
246 /// Deprecated in favor of the probe_library function
247 #[doc(hidden)]
find_library(name: &str) -> Result<Library, String>248 pub fn find_library(name: &str) -> Result<Library, String> {
249     probe_library(name).map_err(|e| e.to_string())
250 }
251 
252 /// Simple shortcut for using all default options for finding a library.
probe_library(name: &str) -> Result<Library, Error>253 pub fn probe_library(name: &str) -> Result<Library, Error> {
254     Config::new().probe(name)
255 }
256 
257 #[doc(hidden)]
258 #[deprecated(note = "use config.target_supported() instance method instead")]
target_supported() -> bool259 pub fn target_supported() -> bool {
260     Config::new().target_supported()
261 }
262 
263 /// Run `pkg-config` to get the value of a variable from a package using
264 /// `--variable`.
265 ///
266 /// The content of `PKG_CONFIG_SYSROOT_DIR` is not injected in paths that are
267 /// returned by `pkg-config --variable`, which makes them unsuitable to use
268 /// during cross-compilation unless specifically designed to be used
269 /// at that time.
get_variable(package: &str, variable: &str) -> Result<String, Error>270 pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> {
271     let arg = format!("--variable={}", variable);
272     let cfg = Config::new();
273     let out = run(cfg.command(package, &[&arg]))?;
274     Ok(str::from_utf8(&out).unwrap().trim_end().to_owned())
275 }
276 
277 impl Config {
278     /// Creates a new set of configuration options which are all initially set
279     /// to "blank".
new() -> Config280     pub fn new() -> Config {
281         Config {
282             statik: None,
283             min_version: Bound::Unbounded,
284             max_version: Bound::Unbounded,
285             extra_args: vec![],
286             print_system_cflags: true,
287             print_system_libs: true,
288             cargo_metadata: true,
289             env_metadata: true,
290         }
291     }
292 
293     /// Indicate whether the `--static` flag should be passed.
294     ///
295     /// This will override the inference from environment variables described in
296     /// the crate documentation.
statik(&mut self, statik: bool) -> &mut Config297     pub fn statik(&mut self, statik: bool) -> &mut Config {
298         self.statik = Some(statik);
299         self
300     }
301 
302     /// Indicate that the library must be at least version `vers`.
atleast_version(&mut self, vers: &str) -> &mut Config303     pub fn atleast_version(&mut self, vers: &str) -> &mut Config {
304         self.min_version = Bound::Included(vers.to_string());
305         self.max_version = Bound::Unbounded;
306         self
307     }
308 
309     /// Indicate that the library must be equal to version `vers`.
exactly_version(&mut self, vers: &str) -> &mut Config310     pub fn exactly_version(&mut self, vers: &str) -> &mut Config {
311         self.min_version = Bound::Included(vers.to_string());
312         self.max_version = Bound::Included(vers.to_string());
313         self
314     }
315 
316     /// Indicate that the library's version must be in `range`.
range_version<'a, R>(&mut self, range: R) -> &mut Config where R: RangeBounds<&'a str>,317     pub fn range_version<'a, R>(&mut self, range: R) -> &mut Config
318     where
319         R: RangeBounds<&'a str>,
320     {
321         self.min_version = match range.start_bound() {
322             Bound::Included(vers) => Bound::Included(vers.to_string()),
323             Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
324             Bound::Unbounded => Bound::Unbounded,
325         };
326         self.max_version = match range.end_bound() {
327             Bound::Included(vers) => Bound::Included(vers.to_string()),
328             Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
329             Bound::Unbounded => Bound::Unbounded,
330         };
331         self
332     }
333 
334     /// Add an argument to pass to pkg-config.
335     ///
336     /// It's placed after all of the arguments generated by this library.
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config337     pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config {
338         self.extra_args.push(arg.as_ref().to_os_string());
339         self
340     }
341 
342     /// Define whether metadata should be emitted for cargo allowing it to
343     /// automatically link the binary. Defaults to `true`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config344     pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
345         self.cargo_metadata = cargo_metadata;
346         self
347     }
348 
349     /// Define whether metadata should be emitted for cargo allowing to
350     /// automatically rebuild when environment variables change. Defaults to
351     /// `true`.
env_metadata(&mut self, env_metadata: bool) -> &mut Config352     pub fn env_metadata(&mut self, env_metadata: bool) -> &mut Config {
353         self.env_metadata = env_metadata;
354         self
355     }
356 
357     /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_LIBS` environment
358     /// variable.
359     ///
360     /// This env var is enabled by default.
print_system_libs(&mut self, print: bool) -> &mut Config361     pub fn print_system_libs(&mut self, print: bool) -> &mut Config {
362         self.print_system_libs = print;
363         self
364     }
365 
366     /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS` environment
367     /// variable.
368     ///
369     /// This env var is enabled by default.
print_system_cflags(&mut self, print: bool) -> &mut Config370     pub fn print_system_cflags(&mut self, print: bool) -> &mut Config {
371         self.print_system_cflags = print;
372         self
373     }
374 
375     /// Deprecated in favor fo the `probe` function
376     #[doc(hidden)]
find(&self, name: &str) -> Result<Library, String>377     pub fn find(&self, name: &str) -> Result<Library, String> {
378         self.probe(name).map_err(|e| e.to_string())
379     }
380 
381     /// Run `pkg-config` to find the library `name`.
382     ///
383     /// This will use all configuration previously set to specify how
384     /// `pkg-config` is run.
probe(&self, name: &str) -> Result<Library, Error>385     pub fn probe(&self, name: &str) -> Result<Library, Error> {
386         let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name));
387         if self.env_var_os(&abort_var_name).is_some() {
388             return Err(Error::EnvNoPkgConfig(abort_var_name));
389         } else if !self.target_supported() {
390             return Err(Error::CrossCompilation);
391         }
392 
393         let mut library = Library::new();
394 
395         let output = run(self.command(name, &["--libs", "--cflags"])).map_err(|e| match e {
396             Error::Failure { command, output } => Error::ProbeFailure {
397                 name: name.to_owned(),
398                 command,
399                 output,
400             },
401             other => other,
402         })?;
403         library.parse_libs_cflags(name, &output, self);
404 
405         let output = run(self.command(name, &["--modversion"]))?;
406         library.parse_modversion(str::from_utf8(&output).unwrap());
407 
408         Ok(library)
409     }
410 
411     /// True if pkg-config is used for the host system, or configured for cross-compilation
target_supported(&self) -> bool412     pub fn target_supported(&self) -> bool {
413         let target = env::var_os("TARGET").unwrap_or_default();
414         let host = env::var_os("HOST").unwrap_or_default();
415 
416         // Only use pkg-config in host == target situations by default (allowing an
417         // override).
418         if host == target {
419             return true;
420         }
421 
422         // pkg-config may not be aware of cross-compilation, and require
423         // a wrapper script that sets up platform-specific prefixes.
424         match self.targeted_env_var("PKG_CONFIG_ALLOW_CROSS") {
425             // don't use pkg-config if explicitly disabled
426             Some(ref val) if val == "0" => false,
427             Some(_) => true,
428             None => {
429                 // if not disabled, and pkg-config is customized,
430                 // then assume it's prepared for cross-compilation
431                 self.targeted_env_var("PKG_CONFIG").is_some()
432                     || self.targeted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
433             }
434         }
435     }
436 
437     /// Deprecated in favor of the top level `get_variable` function
438     #[doc(hidden)]
get_variable(package: &str, variable: &str) -> Result<String, String>439     pub fn get_variable(package: &str, variable: &str) -> Result<String, String> {
440         get_variable(package, variable).map_err(|e| e.to_string())
441     }
442 
targeted_env_var(&self, var_base: &str) -> Option<OsString>443     fn targeted_env_var(&self, var_base: &str) -> Option<OsString> {
444         match (env::var("TARGET"), env::var("HOST")) {
445             (Ok(target), Ok(host)) => {
446                 let kind = if host == target { "HOST" } else { "TARGET" };
447                 let target_u = target.replace("-", "_");
448 
449                 self.env_var_os(&format!("{}_{}", var_base, target))
450                     .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u)))
451                     .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base)))
452                     .or_else(|| self.env_var_os(var_base))
453             }
454             (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => {
455                 self.env_var_os(var_base)
456             }
457             (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => {
458                 panic!(
459                     "HOST or TARGET environment variable is not valid unicode: {:?}",
460                     s
461                 )
462             }
463         }
464     }
465 
env_var_os(&self, name: &str) -> Option<OsString>466     fn env_var_os(&self, name: &str) -> Option<OsString> {
467         if self.env_metadata {
468             println!("cargo:rerun-if-env-changed={}", name);
469         }
470         env::var_os(name)
471     }
472 
is_static(&self, name: &str) -> bool473     fn is_static(&self, name: &str) -> bool {
474         self.statik.unwrap_or_else(|| self.infer_static(name))
475     }
476 
command(&self, name: &str, args: &[&str]) -> Command477     fn command(&self, name: &str, args: &[&str]) -> Command {
478         let exe = self
479             .targeted_env_var("PKG_CONFIG")
480             .unwrap_or_else(|| OsString::from("pkg-config"));
481         let mut cmd = Command::new(exe);
482         if self.is_static(name) {
483             cmd.arg("--static");
484         }
485         cmd.args(args).args(&self.extra_args);
486 
487         if let Some(value) = self.targeted_env_var("PKG_CONFIG_PATH") {
488             cmd.env("PKG_CONFIG_PATH", value);
489         }
490         if let Some(value) = self.targeted_env_var("PKG_CONFIG_LIBDIR") {
491             cmd.env("PKG_CONFIG_LIBDIR", value);
492         }
493         if let Some(value) = self.targeted_env_var("PKG_CONFIG_SYSROOT_DIR") {
494             cmd.env("PKG_CONFIG_SYSROOT_DIR", value);
495         }
496         if self.print_system_libs {
497             cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
498         }
499         if self.print_system_cflags {
500             cmd.env("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1");
501         }
502         cmd.arg(name);
503         match self.min_version {
504             Bound::Included(ref version) => {
505                 cmd.arg(&format!("{} >= {}", name, version));
506             }
507             Bound::Excluded(ref version) => {
508                 cmd.arg(&format!("{} > {}", name, version));
509             }
510             _ => (),
511         }
512         match self.max_version {
513             Bound::Included(ref version) => {
514                 cmd.arg(&format!("{} <= {}", name, version));
515             }
516             Bound::Excluded(ref version) => {
517                 cmd.arg(&format!("{} < {}", name, version));
518             }
519             _ => (),
520         }
521         cmd
522     }
523 
print_metadata(&self, s: &str)524     fn print_metadata(&self, s: &str) {
525         if self.cargo_metadata {
526             println!("cargo:{}", s);
527         }
528     }
529 
infer_static(&self, name: &str) -> bool530     fn infer_static(&self, name: &str) -> bool {
531         let name = envify(name);
532         if self.env_var_os(&format!("{}_STATIC", name)).is_some() {
533             true
534         } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() {
535             false
536         } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() {
537             true
538         } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
539             false
540         } else {
541             false
542         }
543     }
544 }
545 
546 // Implement Default manually since Bound does not implement Default.
547 impl Default for Config {
default() -> Config548     fn default() -> Config {
549         Config {
550             statik: None,
551             min_version: Bound::Unbounded,
552             max_version: Bound::Unbounded,
553             extra_args: vec![],
554             print_system_cflags: false,
555             print_system_libs: false,
556             cargo_metadata: false,
557             env_metadata: false,
558         }
559     }
560 }
561 
562 impl Library {
new() -> Library563     fn new() -> Library {
564         Library {
565             libs: Vec::new(),
566             link_paths: Vec::new(),
567             link_files: Vec::new(),
568             include_paths: Vec::new(),
569             ld_args: Vec::new(),
570             frameworks: Vec::new(),
571             framework_paths: Vec::new(),
572             defines: HashMap::new(),
573             version: String::new(),
574             _priv: (),
575         }
576     }
577 
578     /// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
579     /// using target-specific logic.
extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str>580     fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
581         fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
582             for suffix in suffixes {
583                 if filename.ends_with(suffix) {
584                     return Some(&filename[..filename.len() - suffix.len()]);
585                 }
586             }
587             None
588         }
589 
590         let prefix = "lib";
591         if target.contains("msvc") {
592             // According to link.exe documentation:
593             // https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
594             //
595             //   LINK doesn't use file extensions to make assumptions about the contents of a file.
596             //   Instead, LINK examines each input file to determine what kind of file it is.
597             //
598             // However, rustc appends `.lib` to the string it receives from the -l command line argument,
599             // which it receives from Cargo via cargo:rustc-link-lib:
600             // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
601             // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
602             // So the only file extension that works for MSVC targets is `.lib`
603             return test_suffixes(filename, &[".lib"]);
604         } else if target.contains("windows") && target.contains("gnu") {
605             // GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
606             // which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
607             // receives via the -l command line argument before passing it to the linker:
608             // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
609             // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
610             // GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
611             // GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
612             // the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
613             // https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
614             if filename.starts_with(prefix) {
615                 let filename = &filename[prefix.len()..];
616                 return test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]);
617             } else {
618                 return test_suffixes(filename, &[".dll.a", ".dll", ".lib"]);
619             }
620         } else if target.contains("apple") {
621             if filename.starts_with(prefix) {
622                 let filename = &filename[prefix.len()..];
623                 return test_suffixes(filename, &[".a", ".so", ".dylib"]);
624             }
625             return None;
626         } else {
627             if filename.starts_with(prefix) {
628                 let filename = &filename[prefix.len()..];
629                 return test_suffixes(filename, &[".a", ".so"]);
630             }
631             return None;
632         }
633     }
634 
parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config)635     fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
636         let mut is_msvc = false;
637         let target = env::var("TARGET");
638         if let Ok(target) = &target {
639             if target.contains("msvc") {
640                 is_msvc = true;
641             }
642         }
643 
644         let system_roots = if cfg!(target_os = "macos") {
645             vec![PathBuf::from("/Library"), PathBuf::from("/System")]
646         } else {
647             let sysroot = config
648                 .env_var_os("PKG_CONFIG_SYSROOT_DIR")
649                 .or_else(|| config.env_var_os("SYSROOT"))
650                 .map(PathBuf::from);
651 
652             if cfg!(target_os = "windows") {
653                 if let Some(sysroot) = sysroot {
654                     vec![sysroot]
655                 } else {
656                     vec![]
657                 }
658             } else {
659                 vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
660             }
661         };
662 
663         let mut dirs = Vec::new();
664         let statik = config.is_static(name);
665 
666         let words = split_flags(output);
667 
668         // Handle single-character arguments like `-I/usr/include`
669         let parts = words
670             .iter()
671             .filter(|l| l.len() > 2)
672             .map(|arg| (&arg[0..2], &arg[2..]));
673         for (flag, val) in parts {
674             match flag {
675                 "-L" => {
676                     let meta = format!("rustc-link-search=native={}", val);
677                     config.print_metadata(&meta);
678                     dirs.push(PathBuf::from(val));
679                     self.link_paths.push(PathBuf::from(val));
680                 }
681                 "-F" => {
682                     let meta = format!("rustc-link-search=framework={}", val);
683                     config.print_metadata(&meta);
684                     self.framework_paths.push(PathBuf::from(val));
685                 }
686                 "-I" => {
687                     self.include_paths.push(PathBuf::from(val));
688                 }
689                 "-l" => {
690                     // These are provided by the CRT with MSVC
691                     if is_msvc && ["m", "c", "pthread"].contains(&val) {
692                         continue;
693                     }
694 
695                     if statik && is_static_available(val, &system_roots, &dirs) {
696                         let meta = format!("rustc-link-lib=static={}", val);
697                         config.print_metadata(&meta);
698                     } else {
699                         let meta = format!("rustc-link-lib={}", val);
700                         config.print_metadata(&meta);
701                     }
702 
703                     self.libs.push(val.to_string());
704                 }
705                 "-D" => {
706                     let mut iter = val.split('=');
707                     self.defines.insert(
708                         iter.next().unwrap().to_owned(),
709                         iter.next().map(|s| s.to_owned()),
710                     );
711                 }
712                 _ => {}
713             }
714         }
715 
716         // Handle multi-character arguments with space-separated value like `-framework foo`
717         let mut iter = words.iter().flat_map(|arg| {
718             if arg.starts_with("-Wl,") {
719                 arg[4..].split(',').collect()
720             } else {
721                 vec![arg.as_ref()]
722             }
723         });
724         while let Some(part) = iter.next() {
725             match part {
726                 "-framework" => {
727                     if let Some(lib) = iter.next() {
728                         let meta = format!("rustc-link-lib=framework={}", lib);
729                         config.print_metadata(&meta);
730                         self.frameworks.push(lib.to_string());
731                     }
732                 }
733                 "-isystem" | "-iquote" | "-idirafter" => {
734                     if let Some(inc) = iter.next() {
735                         self.include_paths.push(PathBuf::from(inc));
736                     }
737                 }
738                 _ => {
739                     let path = std::path::Path::new(part);
740                     if path.is_file() {
741                         // Cargo doesn't have a means to directly specify a file path to link,
742                         // so split up the path into the parent directory and library name.
743                         // TODO: pass file path directly when link-arg library type is stabilized
744                         // https://github.com/rust-lang/rust/issues/99427
745                         if let (Some(dir), Some(file_name), Ok(target)) =
746                             (path.parent(), path.file_name(), &target)
747                         {
748                             match Self::extract_lib_from_filename(
749                                 target,
750                                 &file_name.to_string_lossy(),
751                             ) {
752                                 Some(lib_basename) => {
753                                     let link_search =
754                                         format!("rustc-link-search={}", dir.display());
755                                     config.print_metadata(&link_search);
756 
757                                     let link_lib = format!("rustc-link-lib={}", lib_basename);
758                                     config.print_metadata(&link_lib);
759                                     self.link_files.push(PathBuf::from(path));
760                                 }
761                                 None => {
762                                     println!("cargo:warning=File path {} found in pkg-config file for {}, but could not extract library base name to pass to linker command line", path.display(), name);
763                                 }
764                             }
765                         }
766                     }
767                 }
768             }
769         }
770 
771         let mut linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,"));
772         while let Some(option) = linker_options.next() {
773             let mut pop = false;
774             let mut ld_option = vec![];
775             for subopt in option[4..].split(',') {
776                 if pop {
777                     pop = false;
778                     continue;
779                 }
780 
781                 if subopt == "-framework" {
782                     pop = true;
783                     continue;
784                 }
785 
786                 ld_option.push(subopt);
787             }
788 
789             let meta = format!("rustc-link-arg=-Wl,{}", ld_option.join(","));
790             config.print_metadata(&meta);
791 
792             self.ld_args
793                 .push(ld_option.into_iter().map(String::from).collect());
794         }
795     }
796 
parse_modversion(&mut self, output: &str)797     fn parse_modversion(&mut self, output: &str) {
798         self.version.push_str(output.lines().nth(0).unwrap().trim());
799     }
800 }
801 
envify(name: &str) -> String802 fn envify(name: &str) -> String {
803     name.chars()
804         .map(|c| c.to_ascii_uppercase())
805         .map(|c| if c == '-' { '_' } else { c })
806         .collect()
807 }
808 
809 /// System libraries should only be linked dynamically
is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool810 fn is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool {
811     let libname = format!("lib{}.a", name);
812 
813     dirs.iter().any(|dir| {
814         !system_roots.iter().any(|sys| dir.starts_with(sys)) && dir.join(&libname).exists()
815     })
816 }
817 
run(mut cmd: Command) -> Result<Vec<u8>, Error>818 fn run(mut cmd: Command) -> Result<Vec<u8>, Error> {
819     match cmd.output() {
820         Ok(output) => {
821             if output.status.success() {
822                 Ok(output.stdout)
823             } else {
824                 Err(Error::Failure {
825                     command: format!("{:?}", cmd),
826                     output,
827                 })
828             }
829         }
830         Err(cause) => Err(Error::Command {
831             command: format!("{:?}", cmd),
832             cause,
833         }),
834     }
835 }
836 
837 /// Split output produced by pkg-config --cflags and / or --libs into separate flags.
838 ///
839 /// Backslash in output is used to preserve literal meaning of following byte.  Different words are
840 /// separated by unescaped space. Other whitespace characters generally should not occur unescaped
841 /// at all, apart from the newline at the end of output. For compatibility with what others
842 /// consumers of pkg-config output would do in this scenario, they are used here for splitting as
843 /// well.
split_flags(output: &[u8]) -> Vec<String>844 fn split_flags(output: &[u8]) -> Vec<String> {
845     let mut word = Vec::new();
846     let mut words = Vec::new();
847     let mut escaped = false;
848 
849     for &b in output {
850         match b {
851             _ if escaped => {
852                 escaped = false;
853                 word.push(b);
854             }
855             b'\\' => escaped = true,
856             b'\t' | b'\n' | b'\r' | b' ' => {
857                 if !word.is_empty() {
858                     words.push(String::from_utf8(word).unwrap());
859                     word = Vec::new();
860                 }
861             }
862             _ => word.push(b),
863         }
864     }
865 
866     if !word.is_empty() {
867         words.push(String::from_utf8(word).unwrap());
868     }
869 
870     words
871 }
872 
873 #[cfg(test)]
874 mod tests {
875     use super::*;
876 
877     #[test]
878     #[cfg(target_os = "macos")]
system_library_mac_test()879     fn system_library_mac_test() {
880         use std::path::Path;
881 
882         let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
883 
884         assert!(!is_static_available(
885             "PluginManager",
886             &system_roots,
887             &[PathBuf::from("/Library/Frameworks")]
888         ));
889         assert!(!is_static_available(
890             "python2.7",
891             &system_roots,
892             &[PathBuf::from(
893                 "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
894             )]
895         ));
896         assert!(!is_static_available(
897             "ffi_convenience",
898             &system_roots,
899             &[PathBuf::from(
900                 "/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
901             )]
902         ));
903 
904         // Homebrew is in /usr/local, and it's not a part of the OS
905         if Path::new("/usr/local/lib/libpng16.a").exists() {
906             assert!(is_static_available(
907                 "png16",
908                 &system_roots,
909                 &[PathBuf::from("/usr/local/lib")]
910             ));
911 
912             let libpng = Config::new()
913                 .range_version("1".."99")
914                 .probe("libpng16")
915                 .unwrap();
916             assert!(libpng.version.find('\n').is_none());
917         }
918     }
919 
920     #[test]
921     #[cfg(target_os = "linux")]
system_library_linux_test()922     fn system_library_linux_test() {
923         assert!(!is_static_available(
924             "util",
925             &[PathBuf::from("/usr")],
926             &[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
927         ));
928         assert!(!is_static_available(
929             "dialog",
930             &[PathBuf::from("/usr")],
931             &[PathBuf::from("/usr/lib")]
932         ));
933     }
934 
test_library_filename(target: &str, filename: &str)935     fn test_library_filename(target: &str, filename: &str) {
936         assert_eq!(
937             Library::extract_lib_from_filename(target, filename),
938             Some("foo")
939         );
940     }
941 
942     #[test]
link_filename_linux()943     fn link_filename_linux() {
944         let target = "x86_64-unknown-linux-gnu";
945         test_library_filename(target, "libfoo.a");
946         test_library_filename(target, "libfoo.so");
947     }
948 
949     #[test]
link_filename_apple()950     fn link_filename_apple() {
951         let target = "x86_64-apple-darwin";
952         test_library_filename(target, "libfoo.a");
953         test_library_filename(target, "libfoo.so");
954         test_library_filename(target, "libfoo.dylib");
955     }
956 
957     #[test]
link_filename_msvc()958     fn link_filename_msvc() {
959         let target = "x86_64-pc-windows-msvc";
960         // static and dynamic libraries have the same .lib suffix
961         test_library_filename(target, "foo.lib");
962     }
963 
964     #[test]
link_filename_mingw()965     fn link_filename_mingw() {
966         let target = "x86_64-pc-windows-gnu";
967         test_library_filename(target, "foo.lib");
968         test_library_filename(target, "libfoo.a");
969         test_library_filename(target, "foo.dll");
970         test_library_filename(target, "foo.dll.a");
971     }
972 }
973