diff options
Diffstat (limited to 'third_party/pkg-config-rs/src/lib.rs')
-rw-r--r-- | third_party/pkg-config-rs/src/lib.rs | 471 |
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 | |||
56 | use std::ascii::AsciiExt; | ||
57 | use std::env; | ||
58 | use std::error; | ||
59 | use std::ffi::{OsStr, OsString}; | ||
60 | use std::fmt; | ||
61 | use std::fs; | ||
62 | use std::io; | ||
63 | use std::path::{PathBuf, Path}; | ||
64 | use std::process::{Command, Output}; | ||
65 | use std::str; | ||
66 | |||
67 | pub 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)] | ||
80 | pub 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)] | ||
88 | pub 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. | ||
99 | pub 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 | |||
125 | impl 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 | ||
148 | struct OutputDebugger<'a>(&'a Output); | ||
149 | |||
150 | // Lifted from 1.7 std | ||
151 | impl<'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 | ||
174 | impl 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 | |||
200 | impl 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)] | ||
232 | pub 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. | ||
237 | pub 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. | ||
243 | pub 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 | |||
249 | impl 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 | |||
354 | impl 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 | |||
424 | fn 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 | |||
439 | fn envify(name: &str) -> String { | ||
440 | name.chars().map(|c| c.to_ascii_uppercase()).map(|c| { | ||
441 | if c == '-' {'_'} else {c} | ||
442 | }).collect() | ||
443 | } | ||
444 | |||
445 | fn 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 | |||
453 | fn 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 | } | ||