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