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