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 //! * `PKG_CONFIG_ALLOW_CROSS` - if this variable is not set, then `pkg-config`
13 //!   will automatically be disabled for all cross compiles.
14 //! * `FOO_NO_PKG_CONFIG` - if set, this will disable running `pkg-config` when
15 //!   probing for the library named `foo`.
16 //!
17 //! There are also a number of environment variables which can configure how a
18 //! library is linked to (dynamically vs statically). These variables control
19 //! whether the `--static` flag is passed. Note that this behavior can be
20 //! overridden by configuring explicitly on `Config`. The variables are checked
21 //! in the following order:
22 //!
23 //! * `FOO_STATIC` - pass `--static` for the library `foo`
24 //! * `FOO_DYNAMIC` - do not pass `--static` for the library `foo`
25 //! * `PKG_CONFIG_ALL_STATIC` - pass `--static` for all libraries
26 //! * `PKG_CONFIG_ALL_DYNAMIC` - do not pass `--static` for all libraries
27 //!
28 //! After running `pkg-config` all appropriate Cargo metadata will be printed on
29 //! stdout if the search was successful.
30 //!
31 //! # Example
32 //!
33 //! Find the system library named `foo`, with minimum version 1.2.3:
34 //!
35 //! ```no_run
36 //! extern crate pkg_config;
37 //!
38 //! fn main() {
39 //!     pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
40 //! }
41 //! ```
42 //!
43 //! Find the system library named `foo`, with no version requirement (not
44 //! recommended):
45 //!
46 //! ```no_run
47 //! extern crate pkg_config;
48 //!
49 //! fn main() {
50 //!     pkg_config::probe_library("foo").unwrap();
51 //! }
52 //! ```
53 //!
54 //! Configure how library `foo` is linked to.
55 //!
56 //! ```no_run
57 //! extern crate pkg_config;
58 //!
59 //! fn main() {
60 //!     pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
61 //! }
62 //! ```
63 
64 #![doc(html_root_url = "https://docs.rs/pkg-config/0.3")]
65 #![cfg_attr(test, deny(warnings))]
66 
67 use std::ascii::AsciiExt;
68 use std::env;
69 use std::error;
70 use std::ffi::{OsStr, OsString};
71 use std::fmt;
72 use std::fs;
73 use std::io;
74 use std::path::{PathBuf, Path};
75 use std::process::{Command, Output};
76 use std::str;
77 
target_supported() -> bool78 pub fn target_supported() -> bool {
79     let target = env::var("TARGET").unwrap_or(String::new());
80     let host = env::var("HOST").unwrap_or(String::new());
81 
82     // Only use pkg-config in host == target situations by default (allowing an
83     // override) and then also don't use pkg-config on MSVC as it's really not
84     // meant to work there but when building MSVC code in a MSYS shell we may be
85     // able to run pkg-config anyway.
86     (host == target || env::var_os("PKG_CONFIG_ALLOW_CROSS").is_some()) &&
87     !target.contains("msvc")
88 }
89 
90 #[derive(Clone)]
91 pub struct Config {
92     statik: Option<bool>,
93     atleast_version: Option<String>,
94     extra_args: Vec<OsString>,
95     cargo_metadata: bool,
96     print_system_libs: bool,
97 }
98 
99 #[derive(Debug)]
100 pub struct Library {
101     pub libs: Vec<String>,
102     pub link_paths: Vec<PathBuf>,
103     pub frameworks: Vec<String>,
104     pub framework_paths: Vec<PathBuf>,
105     pub include_paths: Vec<PathBuf>,
106     pub version: String,
107     _priv: (),
108 }
109 
110 /// Represents all reasons `pkg-config` might not succeed or be run at all.
111 pub enum Error {
112     /// Aborted because of `*_NO_PKG_CONFIG` environment variable.
113     ///
114     /// Contains the name of the responsible environment variable.
115     EnvNoPkgConfig(String),
116 
117     /// Cross compilation detected.
118     ///
119     /// Override with `PKG_CONFIG_ALLOW_CROSS=1`.
120     CrossCompilation,
121 
122     /// Attempted to compile using the MSVC ABI build
123     MSVC,
124 
125     /// Failed to run `pkg-config`.
126     ///
127     /// Contains the command and the cause.
128     Command { command: String, cause: io::Error },
129 
130     /// `pkg-config` did not exit sucessfully.
131     ///
132     /// Contains the command and output.
133     Failure { command: String, output: Output },
134 
135     #[doc(hidden)]
136     // please don't match on this, we're likely to add more variants over time
137     __Nonexhaustive,
138 }
139 
140 impl error::Error for Error {
description(&self) -> &str141     fn description(&self) -> &str {
142         match *self {
143             Error::EnvNoPkgConfig(_) => "pkg-config requested to be aborted",
144             Error::CrossCompilation => {
145                 "pkg-config doesn't handle cross compilation. \
146                  Use PKG_CONFIG_ALLOW_CROSS=1 to override"
147             }
148             Error::MSVC => "pkg-config is incompatible with the MSVC ABI build.",
149             Error::Command { .. } => "failed to run pkg-config",
150             Error::Failure { .. } => "pkg-config did not exit sucessfully",
151             Error::__Nonexhaustive => panic!(),
152         }
153     }
154 
cause(&self) -> Option<&error::Error>155     fn cause(&self) -> Option<&error::Error> {
156         match *self {
157             Error::Command { ref cause, .. } => Some(cause),
158             _ => None,
159         }
160     }
161 }
162 
163 // Workaround for temporary lack of impl Debug for Output in stable std
164 struct OutputDebugger<'a>(&'a Output);
165 
166 // Lifted from 1.7 std
167 impl<'a> fmt::Debug for OutputDebugger<'a> {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result168     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
169         let stdout_utf8 = str::from_utf8(&self.0.stdout);
170         let stdout_debug: &fmt::Debug = match stdout_utf8 {
171             Ok(ref str) => str,
172             Err(_) => &self.0.stdout
173         };
174 
175         let stderr_utf8 = str::from_utf8(&self.0.stderr);
176         let stderr_debug: &fmt::Debug = match stderr_utf8 {
177             Ok(ref str) => str,
178             Err(_) => &self.0.stderr
179         };
180 
181         fmt.debug_struct("Output")
182            .field("status", &self.0.status)
183            .field("stdout", stdout_debug)
184            .field("stderr", stderr_debug)
185            .finish()
186     }
187 }
188 
189 // Workaround for temporary lack of impl Debug for Output in stable std, continued
190 impl fmt::Debug for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>191     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
192         match *self {
193             Error::EnvNoPkgConfig(ref name) => {
194                 f.debug_tuple("EnvNoPkgConfig")
195                  .field(name)
196                  .finish()
197             }
198             Error::CrossCompilation => write!(f, "CrossCompilation"),
199             Error::MSVC => write!(f, "MSVC"),
200             Error::Command { ref command, ref cause } => {
201                 f.debug_struct("Command")
202                  .field("command", command)
203                  .field("cause", cause)
204                  .finish()
205             }
206             Error::Failure { ref command, ref output } => {
207                 f.debug_struct("Failure")
208                  .field("command", command)
209                  .field("output", &OutputDebugger(output))
210                  .finish()
211             }
212             Error::__Nonexhaustive => panic!(),
213         }
214     }
215 }
216 
217 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>218     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
219         match *self {
220             Error::EnvNoPkgConfig(ref name) => {
221                 write!(f, "Aborted because {} is set", name)
222             }
223             Error::CrossCompilation => {
224                 write!(f, "Cross compilation detected. \
225                        Use PKG_CONFIG_ALLOW_CROSS=1 to override")
226             }
227             Error::MSVC => {
228                 write!(f, "MSVC target detected. If you are using the MSVC ABI \
229                        rust build, please use the GNU ABI build instead.")
230             }
231             Error::Command { ref command, ref cause } => {
232                 write!(f, "Failed to run `{}`: {}", command, cause)
233             }
234             Error::Failure { ref command, ref output } => {
235                 let stdout = str::from_utf8(&output.stdout).unwrap();
236                 let stderr = str::from_utf8(&output.stderr).unwrap();
237                 try!(write!(f, "`{}` did not exit successfully: {}", command, output.status));
238                 if !stdout.is_empty() {
239                     try!(write!(f, "\n--- stdout\n{}", stdout));
240                 }
241                 if !stderr.is_empty() {
242                     try!(write!(f, "\n--- stderr\n{}", stderr));
243                 }
244                 Ok(())
245             }
246             Error::__Nonexhaustive => panic!(),
247         }
248     }
249 }
250 
251 /// Deprecated in favor of the probe_library function
252 #[doc(hidden)]
find_library(name: &str) -> Result<Library, String>253 pub fn find_library(name: &str) -> Result<Library, String> {
254     probe_library(name).map_err(|e| e.to_string())
255 }
256 
257 /// Simple shortcut for using all default options for finding a library.
probe_library(name: &str) -> Result<Library, Error>258 pub fn probe_library(name: &str) -> Result<Library, Error> {
259     Config::new().probe(name)
260 }
261 
262 /// Run `pkg-config` to get the value of a variable from a package using
263 /// --variable.
get_variable(package: &str, variable: &str) -> Result<String, Error>264 pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> {
265     let arg = format!("--variable={}", variable);
266     let cfg = Config::new();
267     Ok(try!(run(cfg.command(package, &[&arg]))).trim_right().to_owned())
268 }
269 
270 impl Config {
271     /// Creates a new set of configuration options which are all initially set
272     /// to "blank".
new() -> Config273     pub fn new() -> Config {
274         Config {
275             statik: None,
276             atleast_version: None,
277             extra_args: vec![],
278             print_system_libs: true,
279             cargo_metadata: true,
280         }
281     }
282 
283     /// Indicate whether the `--static` flag should be passed.
284     ///
285     /// This will override the inference from environment variables described in
286     /// the crate documentation.
statik(&mut self, statik: bool) -> &mut Config287     pub fn statik(&mut self, statik: bool) -> &mut Config {
288         self.statik = Some(statik);
289         self
290     }
291 
292     /// Indicate that the library must be at least version `vers`.
atleast_version(&mut self, vers: &str) -> &mut Config293     pub fn atleast_version(&mut self, vers: &str) -> &mut Config {
294         self.atleast_version = Some(vers.to_string());
295         self
296     }
297 
298     /// Add an argument to pass to pkg-config.
299     ///
300     /// It's placed after all of the arguments generated by this library.
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config301     pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config {
302         self.extra_args.push(arg.as_ref().to_os_string());
303         self
304     }
305 
306     /// Define whether metadata should be emitted for cargo allowing it to
307     /// automatically link the binary. Defaults to `true`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config308     pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
309         self.cargo_metadata = cargo_metadata;
310         self
311     }
312 
313     /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_LIBS` environment
314     /// variable.
315     ///
316     /// This env var is enabled by default.
print_system_libs(&mut self, print: bool) -> &mut Config317     pub fn print_system_libs(&mut self, print: bool) -> &mut Config {
318         self.print_system_libs = print;
319         self
320     }
321 
322     /// Deprecated in favor fo the `probe` function
323     #[doc(hidden)]
find(&self, name: &str) -> Result<Library, String>324     pub fn find(&self, name: &str) -> Result<Library, String> {
325         self.probe(name).map_err(|e| e.to_string())
326     }
327 
328     /// Run `pkg-config` to find the library `name`.
329     ///
330     /// This will use all configuration previously set to specify how
331     /// `pkg-config` is run.
probe(&self, name: &str) -> Result<Library, Error>332     pub fn probe(&self, name: &str) -> Result<Library, Error> {
333         let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name));
334         if env::var_os(&abort_var_name).is_some() {
335             return Err(Error::EnvNoPkgConfig(abort_var_name))
336         } else if !target_supported() {
337             if env::var("TARGET").unwrap_or(String::new()).contains("msvc") {
338                 return Err(Error::MSVC);
339             }
340             else {
341                 return Err(Error::CrossCompilation);
342             }
343         }
344 
345         let mut library = Library::new();
346 
347         let output = try!(run(self.command(name, &["--libs", "--cflags"])));
348         library.parse_libs_cflags(name, &output, self);
349 
350         let output = try!(run(self.command(name, &["--modversion"])));
351         library.parse_modversion(&output);
352 
353         Ok(library)
354     }
355 
356     /// Deprecated in favor of the top level `get_variable` function
357     #[doc(hidden)]
get_variable(package: &str, variable: &str) -> Result<String, String>358     pub fn get_variable(package: &str, variable: &str) -> Result<String, String> {
359         get_variable(package, variable).map_err(|e| e.to_string())
360     }
361 
is_static(&self, name: &str) -> bool362     fn is_static(&self, name: &str) -> bool {
363         self.statik.unwrap_or_else(|| infer_static(name))
364     }
365 
command(&self, name: &str, args: &[&str]) -> Command366     fn command(&self, name: &str, args: &[&str]) -> Command {
367         let exe = env::var("PKG_CONFIG").unwrap_or(String::from("pkg-config"));
368         let mut cmd = Command::new(exe);
369         if self.is_static(name) {
370             cmd.arg("--static");
371         }
372         cmd.args(args)
373            .args(&self.extra_args);
374 
375         if self.print_system_libs {
376             cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
377         }
378         if let Some(ref version) = self.atleast_version {
379             cmd.arg(&format!("{} >= {}", name, version));
380         } else {
381             cmd.arg(name);
382         }
383         cmd
384     }
385 
print_metadata(&self, s: &str)386     fn print_metadata(&self, s: &str) {
387         if self.cargo_metadata {
388             println!("cargo:{}", s);
389         }
390     }
391 }
392 
393 impl Library {
new() -> Library394     fn new() -> Library {
395         Library {
396             libs: Vec::new(),
397             link_paths: Vec::new(),
398             include_paths: Vec::new(),
399             frameworks: Vec::new(),
400             framework_paths: Vec::new(),
401             version: String::new(),
402             _priv: (),
403         }
404     }
405 
parse_libs_cflags(&mut self, name: &str, output: &str, config: &Config)406     fn parse_libs_cflags(&mut self, name: &str, output: &str, config: &Config) {
407         let parts = output.trim_right()
408                           .split(' ')
409                           .filter(|l| l.len() > 2)
410                           .map(|arg| (&arg[0..2], &arg[2..]))
411                           .collect::<Vec<_>>();
412 
413         let mut dirs = Vec::new();
414         let statik = config.is_static(name);
415         for &(flag, val) in parts.iter() {
416             match flag {
417                 "-L" => {
418                     let meta = format!("rustc-link-search=native={}", val);
419                     config.print_metadata(&meta);
420                     dirs.push(PathBuf::from(val));
421                     self.link_paths.push(PathBuf::from(val));
422                 }
423                 "-F" => {
424                     let meta = format!("rustc-link-search=framework={}", val);
425                     config.print_metadata(&meta);
426                     self.framework_paths.push(PathBuf::from(val));
427                 }
428                 "-I" => {
429                     self.include_paths.push(PathBuf::from(val));
430                 }
431                 "-l" => {
432                     self.libs.push(val.to_string());
433                     if statik && !is_system(val, &dirs) {
434                         let meta = format!("rustc-link-lib=static={}", val);
435                         config.print_metadata(&meta);
436                     } else {
437                         let meta = format!("rustc-link-lib={}", val);
438                         config.print_metadata(&meta);
439                     }
440                 }
441                 _ => {}
442             }
443         }
444 
445         let mut iter = output.trim_right().split(' ');
446         while let Some(part) = iter.next() {
447             if part != "-framework" {
448                 continue
449             }
450             if let Some(lib) = iter.next() {
451                 let meta = format!("rustc-link-lib=framework={}", lib);
452                 config.print_metadata(&meta);
453                 self.frameworks.push(lib.to_string());
454             }
455         }
456     }
457 
parse_modversion(&mut self, output: &str)458     fn parse_modversion(&mut self, output: &str) {
459         self.version.push_str(output.trim());
460     }
461 }
462 
infer_static(name: &str) -> bool463 fn infer_static(name: &str) -> bool {
464     let name = envify(name);
465     if env::var_os(&format!("{}_STATIC", name)).is_some() {
466         true
467     } else if env::var_os(&format!("{}_DYNAMIC", name)).is_some() {
468         false
469     } else if env::var_os("PKG_CONFIG_ALL_STATIC").is_some() {
470         true
471     } else if env::var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
472         false
473     } else {
474         false
475     }
476 }
477 
envify(name: &str) -> String478 fn envify(name: &str) -> String {
479     name.chars().map(|c| c.to_ascii_uppercase()).map(|c| {
480         if c == '-' {'_'} else {c}
481     }).collect()
482 }
483 
is_system(name: &str, dirs: &[PathBuf]) -> bool484 fn is_system(name: &str, dirs: &[PathBuf]) -> bool {
485     let libname = format!("lib{}.a", name);
486     let root = Path::new("/usr");
487     !dirs.iter().any(|d| {
488         !d.starts_with(root) && fs::metadata(&d.join(&libname)).is_ok()
489     })
490 }
491 
run(mut cmd: Command) -> Result<String, Error>492 fn run(mut cmd: Command) -> Result<String, Error> {
493     match cmd.output() {
494         Ok(output) => {
495             if output.status.success() {
496                 let stdout = String::from_utf8(output.stdout).unwrap();
497                 Ok(stdout)
498             } else {
499                 Err(Error::Failure {
500                     command: format!("{:?}", cmd),
501                     output: output,
502                 })
503             }
504         }
505         Err(cause) => Err(Error::Command {
506             command: format!("{:?}", cmd),
507             cause: cause,
508         }),
509     }
510 }
511