]>
Commit | Line | Data |
---|---|---|
d76f7d0b XL |
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 | //! | |
d76f7d0b XL |
12 | //! * `FOO_NO_PKG_CONFIG` - if set, this will disable running `pkg-config` when |
13 | //! probing for the library named `foo`. | |
14 | //! | |
15 | //! There are also a number of environment variables which can configure how a | |
16 | //! library is linked to (dynamically vs statically). These variables control | |
17 | //! whether the `--static` flag is passed. Note that this behavior can be | |
18 | //! overridden by configuring explicitly on `Config`. The variables are checked | |
19 | //! in the following order: | |
20 | //! | |
21 | //! * `FOO_STATIC` - pass `--static` for the library `foo` | |
22 | //! * `FOO_DYNAMIC` - do not pass `--static` for the library `foo` | |
23 | //! * `PKG_CONFIG_ALL_STATIC` - pass `--static` for all libraries | |
24 | //! * `PKG_CONFIG_ALL_DYNAMIC` - do not pass `--static` for all libraries | |
25 | //! | |
26 | //! After running `pkg-config` all appropriate Cargo metadata will be printed on | |
27 | //! stdout if the search was successful. | |
28 | //! | |
29 | //! # Example | |
30 | //! | |
3ee932bc VK |
31 | //! Find the system library named `foo`, with minimum version 1.2.3: |
32 | //! | |
33 | //! ```no_run | |
34 | //! extern crate pkg_config; | |
35 | //! | |
36 | //! fn main() { | |
37 | //! pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap(); | |
38 | //! } | |
39 | //! ``` | |
40 | //! | |
41 | //! Find the system library named `foo`, with no version requirement (not | |
42 | //! recommended): | |
d76f7d0b XL |
43 | //! |
44 | //! ```no_run | |
45 | //! extern crate pkg_config; | |
46 | //! | |
47 | //! fn main() { | |
48 | //! pkg_config::probe_library("foo").unwrap(); | |
49 | //! } | |
50 | //! ``` | |
51 | //! | |
52 | //! Configure how library `foo` is linked to. | |
53 | //! | |
54 | //! ```no_run | |
55 | //! extern crate pkg_config; | |
56 | //! | |
57 | //! fn main() { | |
3ee932bc | 58 | //! pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap(); |
d76f7d0b XL |
59 | //! } |
60 | //! ``` | |
61 | ||
3ee932bc | 62 | #![doc(html_root_url = "https://docs.rs/pkg-config/0.3")] |
d76f7d0b | 63 | |
f8611cf8 | 64 | use std::collections::HashMap; |
d76f7d0b XL |
65 | use std::env; |
66 | use std::error; | |
67 | use std::ffi::{OsStr, OsString}; | |
68 | use std::fmt; | |
d76f7d0b | 69 | use std::io; |
217acde9 | 70 | use std::ops::{Bound, RangeBounds}; |
c8d4b494 | 71 | use std::path::PathBuf; |
d76f7d0b XL |
72 | use std::process::{Command, Output}; |
73 | use std::str; | |
74 | ||
217acde9 | 75 | #[derive(Clone, Debug)] |
d76f7d0b XL |
76 | pub struct Config { |
77 | statik: Option<bool>, | |
217acde9 XL |
78 | min_version: Bound<String>, |
79 | max_version: Bound<String>, | |
d76f7d0b XL |
80 | extra_args: Vec<OsString>, |
81 | cargo_metadata: bool, | |
f8611cf8 | 82 | env_metadata: bool, |
3ee932bc | 83 | print_system_libs: bool, |
217acde9 | 84 | print_system_cflags: bool, |
d76f7d0b XL |
85 | } |
86 | ||
217acde9 | 87 | #[derive(Clone, Debug)] |
d76f7d0b XL |
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>, | |
62c5094d | 94 | pub ld_args: Vec<Vec<String>>, |
f8611cf8 | 95 | pub defines: HashMap<String, Option<String>>, |
d76f7d0b XL |
96 | pub version: String, |
97 | _priv: (), | |
98 | } | |
99 | ||
100 | /// Represents all reasons `pkg-config` might not succeed or be run at all. | |
101 | pub enum Error { | |
102 | /// Aborted because of `*_NO_PKG_CONFIG` environment variable. | |
103 | /// | |
104 | /// Contains the name of the responsible environment variable. | |
105 | EnvNoPkgConfig(String), | |
106 | ||
f5bb8b5f XL |
107 | /// Cross compilation detected. Kept for compatibility; |
108 | /// the Debian package never emits this. | |
d76f7d0b XL |
109 | CrossCompilation, |
110 | ||
111 | /// Failed to run `pkg-config`. | |
112 | /// | |
113 | /// Contains the command and the cause. | |
114 | Command { command: String, cause: io::Error }, | |
115 | ||
62c5094d | 116 | /// `pkg-config` did not exit sucessfully after probing a library. |
d76f7d0b XL |
117 | /// |
118 | /// Contains the command and output. | |
119 | Failure { command: String, output: Output }, | |
120 | ||
62c5094d FG |
121 | /// `pkg-config` did not exit sucessfully on the first attempt to probe a library. |
122 | /// | |
123 | /// Contains the command and output. | |
124 | ProbeFailure { | |
125 | name: String, | |
126 | command: String, | |
127 | output: Output, | |
128 | }, | |
129 | ||
d76f7d0b XL |
130 | #[doc(hidden)] |
131 | // please don't match on this, we're likely to add more variants over time | |
132 | __Nonexhaustive, | |
133 | } | |
134 | ||
f5bb8b5f | 135 | impl error::Error for Error {} |
d76f7d0b | 136 | |
c8d4b494 XL |
137 | impl fmt::Debug for Error { |
138 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
139 | // Failed `unwrap()` prints Debug representation, but the default debug format lacks helpful instructions for the end users | |
140 | <Error as fmt::Display>::fmt(self, f) | |
141 | } | |
142 | } | |
143 | ||
d76f7d0b XL |
144 | impl fmt::Display for Error { |
145 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
146 | match *self { | |
217acde9 | 147 | Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name), |
217acde9 XL |
148 | Error::Command { |
149 | ref command, | |
150 | ref cause, | |
c8d4b494 XL |
151 | } => { |
152 | match cause.kind() { | |
153 | io::ErrorKind::NotFound => { | |
62c5094d FG |
154 | let crate_name = |
155 | std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "sys".to_owned()); | |
c8d4b494 XL |
156 | let instructions = if cfg!(target_os = "macos") || cfg!(target_os = "ios") { |
157 | "Try `brew install pkg-config` if you have Homebrew.\n" | |
158 | } else if cfg!(unix) { | |
159 | "Try `apt install pkg-config`, or `yum install pkg-config`,\n\ | |
160 | or `pkg install pkg-config` depending on your distribution.\n" | |
161 | } else { | |
162 | "" // There's no easy fix for Windows users | |
163 | }; | |
164 | write!(f, "Could not run `{command}`\n\ | |
165 | The pkg-config command could not be found.\n\ | |
166 | \n\ | |
167 | Most likely, you need to install a pkg-config package for your OS.\n\ | |
168 | {instructions}\ | |
169 | \n\ | |
170 | If you've already installed it, ensure the pkg-config command is one of the\n\ | |
171 | directories in the PATH environment variable.\n\ | |
172 | \n\ | |
173 | If you did not expect this build to link to a pre-installed system library,\n\ | |
174 | then check documentation of the {crate_name} crate for an option to\n\ | |
175 | build the library from source, or disable features or dependencies\n\ | |
176 | that require pkg-config.", command = command, instructions = instructions, crate_name = crate_name) | |
177 | } | |
178 | _ => write!(f, "Failed to run command `{}`, because: {}", command, cause), | |
179 | } | |
180 | } | |
62c5094d FG |
181 | Error::ProbeFailure { |
182 | ref name, | |
183 | ref command, | |
184 | ref output, | |
185 | } => { | |
186 | write!( | |
187 | f, | |
188 | "`{}` did not exit successfully: {}\nerror: could not find system library '{}' required by the '{}' crate\n", | |
189 | command, output.status, name, env::var("CARGO_PKG_NAME").unwrap_or_default(), | |
190 | )?; | |
191 | format_output(output, f) | |
192 | } | |
217acde9 XL |
193 | Error::Failure { |
194 | ref command, | |
195 | ref output, | |
196 | } => { | |
217acde9 XL |
197 | write!( |
198 | f, | |
199 | "`{}` did not exit successfully: {}", | |
200 | command, output.status | |
201 | )?; | |
62c5094d | 202 | format_output(output, f) |
d76f7d0b | 203 | } |
f5bb8b5f | 204 | Error::CrossCompilation | Error::__Nonexhaustive => panic!(), |
d76f7d0b XL |
205 | } |
206 | } | |
207 | } | |
208 | ||
62c5094d FG |
209 | fn format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result { |
210 | let stdout = String::from_utf8_lossy(&output.stdout); | |
211 | if !stdout.is_empty() { | |
212 | write!(f, "\n--- stdout\n{}", stdout)?; | |
213 | } | |
214 | let stderr = String::from_utf8_lossy(&output.stderr); | |
215 | if !stderr.is_empty() { | |
216 | write!(f, "\n--- stderr\n{}", stderr)?; | |
217 | } | |
218 | Ok(()) | |
219 | } | |
220 | ||
d76f7d0b XL |
221 | /// Deprecated in favor of the probe_library function |
222 | #[doc(hidden)] | |
223 | pub fn find_library(name: &str) -> Result<Library, String> { | |
224 | probe_library(name).map_err(|e| e.to_string()) | |
225 | } | |
226 | ||
227 | /// Simple shortcut for using all default options for finding a library. | |
228 | pub fn probe_library(name: &str) -> Result<Library, Error> { | |
229 | Config::new().probe(name) | |
230 | } | |
231 | ||
62c5094d FG |
232 | #[doc(hidden)] |
233 | #[deprecated(note = "use config.target_supported() instance method instead")] | |
234 | pub fn target_supported() -> bool { | |
235 | Config::new().target_supported() | |
236 | } | |
237 | ||
d76f7d0b | 238 | /// Run `pkg-config` to get the value of a variable from a package using |
c8d4b494 XL |
239 | /// `--variable`. |
240 | /// | |
241 | /// The content of `PKG_CONFIG_SYSROOT_DIR` is not injected in paths that are | |
242 | /// returned by `pkg-config --variable`, which makes them unsuitable to use | |
243 | /// during cross-compilation unless specifically designed to be used | |
244 | /// at that time. | |
d76f7d0b XL |
245 | pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> { |
246 | let arg = format!("--variable={}", variable); | |
247 | let cfg = Config::new(); | |
f8611cf8 | 248 | let out = run(cfg.command(package, &[&arg]))?; |
217acde9 | 249 | Ok(str::from_utf8(&out).unwrap().trim_end().to_owned()) |
d76f7d0b XL |
250 | } |
251 | ||
252 | impl Config { | |
253 | /// Creates a new set of configuration options which are all initially set | |
254 | /// to "blank". | |
255 | pub fn new() -> Config { | |
256 | Config { | |
257 | statik: None, | |
217acde9 XL |
258 | min_version: Bound::Unbounded, |
259 | max_version: Bound::Unbounded, | |
d76f7d0b | 260 | extra_args: vec![], |
217acde9 | 261 | print_system_cflags: true, |
3ee932bc | 262 | print_system_libs: true, |
d76f7d0b | 263 | cargo_metadata: true, |
f5bb8b5f | 264 | env_metadata: true, |
d76f7d0b XL |
265 | } |
266 | } | |
267 | ||
268 | /// Indicate whether the `--static` flag should be passed. | |
269 | /// | |
270 | /// This will override the inference from environment variables described in | |
271 | /// the crate documentation. | |
272 | pub fn statik(&mut self, statik: bool) -> &mut Config { | |
273 | self.statik = Some(statik); | |
274 | self | |
275 | } | |
276 | ||
277 | /// Indicate that the library must be at least version `vers`. | |
278 | pub fn atleast_version(&mut self, vers: &str) -> &mut Config { | |
217acde9 XL |
279 | self.min_version = Bound::Included(vers.to_string()); |
280 | self.max_version = Bound::Unbounded; | |
281 | self | |
282 | } | |
283 | ||
284 | /// Indicate that the library must be equal to version `vers`. | |
285 | pub fn exactly_version(&mut self, vers: &str) -> &mut Config { | |
286 | self.min_version = Bound::Included(vers.to_string()); | |
287 | self.max_version = Bound::Included(vers.to_string()); | |
288 | self | |
289 | } | |
290 | ||
291 | /// Indicate that the library's version must be in `range`. | |
292 | pub fn range_version<'a, R>(&mut self, range: R) -> &mut Config | |
293 | where | |
294 | R: RangeBounds<&'a str>, | |
295 | { | |
296 | self.min_version = match range.start_bound() { | |
297 | Bound::Included(vers) => Bound::Included(vers.to_string()), | |
298 | Bound::Excluded(vers) => Bound::Excluded(vers.to_string()), | |
299 | Bound::Unbounded => Bound::Unbounded, | |
300 | }; | |
301 | self.max_version = match range.end_bound() { | |
302 | Bound::Included(vers) => Bound::Included(vers.to_string()), | |
303 | Bound::Excluded(vers) => Bound::Excluded(vers.to_string()), | |
304 | Bound::Unbounded => Bound::Unbounded, | |
305 | }; | |
d76f7d0b XL |
306 | self |
307 | } | |
308 | ||
309 | /// Add an argument to pass to pkg-config. | |
310 | /// | |
311 | /// It's placed after all of the arguments generated by this library. | |
312 | pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config { | |
313 | self.extra_args.push(arg.as_ref().to_os_string()); | |
314 | self | |
315 | } | |
316 | ||
317 | /// Define whether metadata should be emitted for cargo allowing it to | |
318 | /// automatically link the binary. Defaults to `true`. | |
319 | pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config { | |
320 | self.cargo_metadata = cargo_metadata; | |
321 | self | |
322 | } | |
323 | ||
f8611cf8 VK |
324 | /// Define whether metadata should be emitted for cargo allowing to |
325 | /// automatically rebuild when environment variables change. Defaults to | |
f5bb8b5f | 326 | /// `true`. |
f8611cf8 VK |
327 | pub fn env_metadata(&mut self, env_metadata: bool) -> &mut Config { |
328 | self.env_metadata = env_metadata; | |
329 | self | |
330 | } | |
331 | ||
3ee932bc VK |
332 | /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_LIBS` environment |
333 | /// variable. | |
334 | /// | |
335 | /// This env var is enabled by default. | |
336 | pub fn print_system_libs(&mut self, print: bool) -> &mut Config { | |
337 | self.print_system_libs = print; | |
338 | self | |
339 | } | |
340 | ||
217acde9 XL |
341 | /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS` environment |
342 | /// variable. | |
343 | /// | |
344 | /// This env var is enabled by default. | |
345 | pub fn print_system_cflags(&mut self, print: bool) -> &mut Config { | |
346 | self.print_system_cflags = print; | |
347 | self | |
348 | } | |
349 | ||
d76f7d0b XL |
350 | /// Deprecated in favor fo the `probe` function |
351 | #[doc(hidden)] | |
352 | pub fn find(&self, name: &str) -> Result<Library, String> { | |
353 | self.probe(name).map_err(|e| e.to_string()) | |
354 | } | |
355 | ||
356 | /// Run `pkg-config` to find the library `name`. | |
357 | /// | |
358 | /// This will use all configuration previously set to specify how | |
359 | /// `pkg-config` is run. | |
360 | pub fn probe(&self, name: &str) -> Result<Library, Error> { | |
361 | let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name)); | |
f8611cf8 | 362 | if self.env_var_os(&abort_var_name).is_some() { |
217acde9 XL |
363 | return Err(Error::EnvNoPkgConfig(abort_var_name)); |
364 | } else if !self.target_supported() { | |
884caeef | 365 | return Err(Error::CrossCompilation); |
d76f7d0b XL |
366 | } |
367 | ||
368 | let mut library = Library::new(); | |
369 | ||
62c5094d FG |
370 | let output = run(self.command(name, &["--libs", "--cflags"])).map_err(|e| match e { |
371 | Error::Failure { command, output } => Error::ProbeFailure { | |
372 | name: name.to_owned(), | |
373 | command, | |
374 | output, | |
375 | }, | |
376 | other => other, | |
377 | })?; | |
d76f7d0b XL |
378 | library.parse_libs_cflags(name, &output, self); |
379 | ||
f8611cf8 VK |
380 | let output = run(self.command(name, &["--modversion"]))?; |
381 | library.parse_modversion(str::from_utf8(&output).unwrap()); | |
d76f7d0b XL |
382 | |
383 | Ok(library) | |
384 | } | |
385 | ||
62c5094d | 386 | /// True if pkg-config is used for the host system, or configured for cross-compilation |
217acde9 | 387 | pub fn target_supported(&self) -> bool { |
f5bb8b5f XL |
388 | let target = env::var_os("TARGET").unwrap_or_default(); |
389 | let host = env::var_os("HOST").unwrap_or_default(); | |
217acde9 XL |
390 | |
391 | // Only use pkg-config in host == target situations by default (allowing an | |
392 | // override). | |
393 | if host == target { | |
394 | return true; | |
395 | } | |
f5bb8b5f XL |
396 | // always enable PKG_CONFIG_ALLOW_CROSS override in Debian |
397 | return true; | |
217acde9 XL |
398 | |
399 | // pkg-config may not be aware of cross-compilation, and require | |
400 | // a wrapper script that sets up platform-specific prefixes. | |
f5bb8b5f XL |
401 | match self.targetted_env_var("PKG_CONFIG_ALLOW_CROSS") { |
402 | // don't use pkg-config if explicitly disabled | |
403 | Some(ref val) if val == "0" => false, | |
404 | Some(_) => true, | |
405 | None => { | |
406 | // if not disabled, and pkg-config is customized, | |
407 | // then assume it's prepared for cross-compilation | |
408 | self.targetted_env_var("PKG_CONFIG").is_some() | |
409 | || self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some() | |
410 | } | |
411 | } | |
217acde9 XL |
412 | } |
413 | ||
d76f7d0b XL |
414 | /// Deprecated in favor of the top level `get_variable` function |
415 | #[doc(hidden)] | |
416 | pub fn get_variable(package: &str, variable: &str) -> Result<String, String> { | |
417 | get_variable(package, variable).map_err(|e| e.to_string()) | |
418 | } | |
419 | ||
f5bb8b5f XL |
420 | fn targetted_env_var(&self, var_base: &str) -> Option<OsString> { |
421 | match (env::var("TARGET"), env::var("HOST")) { | |
422 | (Ok(target), Ok(host)) => { | |
423 | let kind = if host == target { "HOST" } else { "TARGET" }; | |
424 | let target_u = target.replace("-", "_"); | |
f8611cf8 | 425 | |
f5bb8b5f XL |
426 | self.env_var_os(&format!("{}_{}", var_base, target)) |
427 | .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u))) | |
428 | .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base))) | |
429 | .or_else(|| self.env_var_os(var_base)) | |
430 | } | |
431 | (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => { | |
432 | self.env_var_os(var_base) | |
433 | } | |
434 | (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => { | |
435 | panic!( | |
436 | "HOST or TARGET environment variable is not valid unicode: {:?}", | |
437 | s | |
438 | ) | |
439 | } | |
f8611cf8 | 440 | } |
f8611cf8 VK |
441 | } |
442 | ||
443 | fn env_var_os(&self, name: &str) -> Option<OsString> { | |
444 | if self.env_metadata { | |
445 | println!("cargo:rerun-if-env-changed={}", name); | |
446 | } | |
447 | env::var_os(name) | |
448 | } | |
449 | ||
d76f7d0b | 450 | fn is_static(&self, name: &str) -> bool { |
f8611cf8 | 451 | self.statik.unwrap_or_else(|| self.infer_static(name)) |
d76f7d0b XL |
452 | } |
453 | ||
454 | fn command(&self, name: &str, args: &[&str]) -> Command { | |
f5bb8b5f | 455 | let exe = self |
c8d4b494 | 456 | .targetted_env_var("PKG_CONFIG") |
f5bb8b5f XL |
457 | .unwrap_or_else(|| { |
458 | self.env_var_os("DEB_HOST_GNU_TYPE") | |
459 | .map(|mut t| { t.push(OsString::from("-pkg-config")); t }) | |
460 | .unwrap_or_else(|| OsString::from("pkg-config")) | |
461 | }); | |
d76f7d0b XL |
462 | let mut cmd = Command::new(exe); |
463 | if self.is_static(name) { | |
464 | cmd.arg("--static"); | |
465 | } | |
217acde9 | 466 | cmd.args(args).args(&self.extra_args); |
3ee932bc | 467 | |
f5bb8b5f | 468 | if let Some(value) = self.targetted_env_var("PKG_CONFIG_PATH") { |
f8611cf8 VK |
469 | cmd.env("PKG_CONFIG_PATH", value); |
470 | } | |
f5bb8b5f | 471 | if let Some(value) = self.targetted_env_var("PKG_CONFIG_LIBDIR") { |
f8611cf8 VK |
472 | cmd.env("PKG_CONFIG_LIBDIR", value); |
473 | } | |
f5bb8b5f | 474 | if let Some(value) = self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR") { |
f8611cf8 VK |
475 | cmd.env("PKG_CONFIG_SYSROOT_DIR", value); |
476 | } | |
3ee932bc VK |
477 | if self.print_system_libs { |
478 | cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1"); | |
479 | } | |
217acde9 XL |
480 | if self.print_system_cflags { |
481 | cmd.env("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1"); | |
482 | } | |
483 | cmd.arg(name); | |
484 | match self.min_version { | |
485 | Bound::Included(ref version) => { | |
486 | cmd.arg(&format!("{} >= {}", name, version)); | |
487 | } | |
488 | Bound::Excluded(ref version) => { | |
489 | cmd.arg(&format!("{} > {}", name, version)); | |
490 | } | |
491 | _ => (), | |
492 | } | |
493 | match self.max_version { | |
494 | Bound::Included(ref version) => { | |
495 | cmd.arg(&format!("{} <= {}", name, version)); | |
496 | } | |
497 | Bound::Excluded(ref version) => { | |
498 | cmd.arg(&format!("{} < {}", name, version)); | |
499 | } | |
500 | _ => (), | |
d76f7d0b XL |
501 | } |
502 | cmd | |
503 | } | |
504 | ||
505 | fn print_metadata(&self, s: &str) { | |
506 | if self.cargo_metadata { | |
507 | println!("cargo:{}", s); | |
508 | } | |
509 | } | |
f8611cf8 VK |
510 | |
511 | fn infer_static(&self, name: &str) -> bool { | |
512 | let name = envify(name); | |
513 | if self.env_var_os(&format!("{}_STATIC", name)).is_some() { | |
514 | true | |
515 | } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() { | |
516 | false | |
517 | } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() { | |
518 | true | |
519 | } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() { | |
520 | false | |
521 | } else { | |
522 | false | |
523 | } | |
524 | } | |
d76f7d0b XL |
525 | } |
526 | ||
217acde9 XL |
527 | // Implement Default manualy since Bound does not implement Default. |
528 | impl Default for Config { | |
529 | fn default() -> Config { | |
530 | Config { | |
531 | statik: None, | |
532 | min_version: Bound::Unbounded, | |
533 | max_version: Bound::Unbounded, | |
534 | extra_args: vec![], | |
535 | print_system_cflags: false, | |
536 | print_system_libs: false, | |
537 | cargo_metadata: false, | |
538 | env_metadata: false, | |
539 | } | |
540 | } | |
541 | } | |
542 | ||
d76f7d0b XL |
543 | impl Library { |
544 | fn new() -> Library { | |
545 | Library { | |
546 | libs: Vec::new(), | |
547 | link_paths: Vec::new(), | |
548 | include_paths: Vec::new(), | |
62c5094d | 549 | ld_args: Vec::new(), |
d76f7d0b XL |
550 | frameworks: Vec::new(), |
551 | framework_paths: Vec::new(), | |
f8611cf8 | 552 | defines: HashMap::new(), |
d76f7d0b XL |
553 | version: String::new(), |
554 | _priv: (), | |
555 | } | |
556 | } | |
557 | ||
f8611cf8 | 558 | fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) { |
836c94ab VK |
559 | let mut is_msvc = false; |
560 | if let Ok(target) = env::var("TARGET") { | |
561 | if target.contains("msvc") { | |
562 | is_msvc = true; | |
563 | } | |
564 | } | |
565 | ||
c8d4b494 XL |
566 | let system_roots = if cfg!(target_os = "macos") { |
567 | vec![PathBuf::from("/Library"), PathBuf::from("/System")] | |
568 | } else { | |
569 | let sysroot = config | |
570 | .env_var_os("PKG_CONFIG_SYSROOT_DIR") | |
571 | .or_else(|| config.env_var_os("SYSROOT")) | |
572 | .map(PathBuf::from); | |
573 | ||
574 | if cfg!(target_os = "windows") { | |
575 | if let Some(sysroot) = sysroot { | |
576 | vec![sysroot] | |
577 | } else { | |
578 | vec![] | |
579 | } | |
580 | } else { | |
581 | vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))] | |
582 | } | |
583 | }; | |
584 | ||
585 | let mut dirs = Vec::new(); | |
586 | let statik = config.is_static(name); | |
587 | ||
f8611cf8 | 588 | let words = split_flags(output); |
c8d4b494 XL |
589 | |
590 | // Handle single-character arguments like `-I/usr/include` | |
217acde9 XL |
591 | let parts = words |
592 | .iter() | |
593 | .filter(|l| l.len() > 2) | |
c8d4b494 XL |
594 | .map(|arg| (&arg[0..2], &arg[2..])); |
595 | for (flag, val) in parts { | |
d76f7d0b XL |
596 | match flag { |
597 | "-L" => { | |
598 | let meta = format!("rustc-link-search=native={}", val); | |
599 | config.print_metadata(&meta); | |
600 | dirs.push(PathBuf::from(val)); | |
601 | self.link_paths.push(PathBuf::from(val)); | |
602 | } | |
603 | "-F" => { | |
604 | let meta = format!("rustc-link-search=framework={}", val); | |
605 | config.print_metadata(&meta); | |
606 | self.framework_paths.push(PathBuf::from(val)); | |
607 | } | |
608 | "-I" => { | |
609 | self.include_paths.push(PathBuf::from(val)); | |
610 | } | |
611 | "-l" => { | |
c77be844 VK |
612 | // These are provided by the CRT with MSVC |
613 | if is_msvc && ["m", "c", "pthread"].contains(&val) { | |
614 | continue; | |
615 | } | |
836c94ab | 616 | |
c8d4b494 | 617 | if statik && is_static_available(val, &system_roots, &dirs) { |
c77be844 | 618 | let meta = format!("rustc-link-lib=static={}", val); |
d76f7d0b XL |
619 | config.print_metadata(&meta); |
620 | } else { | |
c77be844 | 621 | let meta = format!("rustc-link-lib={}", val); |
d76f7d0b XL |
622 | config.print_metadata(&meta); |
623 | } | |
836c94ab | 624 | |
c77be844 | 625 | self.libs.push(val.to_string()); |
d76f7d0b | 626 | } |
f8611cf8 | 627 | "-D" => { |
217acde9 XL |
628 | let mut iter = val.split('='); |
629 | self.defines.insert( | |
630 | iter.next().unwrap().to_owned(), | |
631 | iter.next().map(|s| s.to_owned()), | |
632 | ); | |
f8611cf8 | 633 | } |
d76f7d0b XL |
634 | _ => {} |
635 | } | |
636 | } | |
637 | ||
c8d4b494 | 638 | // Handle multi-character arguments with space-separated value like `-framework foo` |
217acde9 XL |
639 | let mut iter = words.iter().flat_map(|arg| { |
640 | if arg.starts_with("-Wl,") { | |
641 | arg[4..].split(',').collect() | |
642 | } else { | |
643 | vec![arg.as_ref()] | |
644 | } | |
645 | }); | |
d76f7d0b | 646 | while let Some(part) = iter.next() { |
c8d4b494 XL |
647 | match part { |
648 | "-framework" => { | |
649 | if let Some(lib) = iter.next() { | |
650 | let meta = format!("rustc-link-lib=framework={}", lib); | |
651 | config.print_metadata(&meta); | |
652 | self.frameworks.push(lib.to_string()); | |
653 | } | |
654 | } | |
655 | "-isystem" | "-iquote" | "-idirafter" => { | |
656 | if let Some(inc) = iter.next() { | |
657 | self.include_paths.push(PathBuf::from(inc)); | |
658 | } | |
659 | } | |
660 | _ => (), | |
d76f7d0b XL |
661 | } |
662 | } | |
62c5094d FG |
663 | |
664 | let mut linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,")); | |
665 | while let Some(option) = linker_options.next() { | |
666 | let mut pop = false; | |
667 | let mut ld_option = vec![]; | |
668 | for subopt in option[4..].split(',') { | |
669 | if pop { | |
670 | pop = false; | |
671 | continue; | |
672 | } | |
673 | ||
674 | if subopt == "-framework" { | |
675 | pop = true; | |
676 | continue; | |
677 | } | |
678 | ||
679 | ld_option.push(subopt); | |
680 | } | |
681 | ||
682 | let meta = format!("rustc-link-arg=-Wl,{}", ld_option.join(",")); | |
683 | config.print_metadata(&meta); | |
684 | ||
685 | self.ld_args | |
686 | .push(ld_option.into_iter().map(String::from).collect()); | |
687 | } | |
d76f7d0b XL |
688 | } |
689 | ||
690 | fn parse_modversion(&mut self, output: &str) { | |
217acde9 | 691 | self.version.push_str(output.lines().nth(0).unwrap().trim()); |
d76f7d0b XL |
692 | } |
693 | } | |
694 | ||
d76f7d0b | 695 | fn envify(name: &str) -> String { |
217acde9 XL |
696 | name.chars() |
697 | .map(|c| c.to_ascii_uppercase()) | |
698 | .map(|c| if c == '-' { '_' } else { c }) | |
699 | .collect() | |
d76f7d0b XL |
700 | } |
701 | ||
f8611cf8 | 702 | /// System libraries should only be linked dynamically |
c8d4b494 | 703 | fn is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool { |
d76f7d0b | 704 | let libname = format!("lib{}.a", name); |
f8611cf8 VK |
705 | |
706 | dirs.iter().any(|dir| { | |
217acde9 | 707 | !system_roots.iter().any(|sys| dir.starts_with(sys)) && dir.join(&libname).exists() |
d76f7d0b XL |
708 | }) |
709 | } | |
710 | ||
f8611cf8 | 711 | fn run(mut cmd: Command) -> Result<Vec<u8>, Error> { |
d76f7d0b XL |
712 | match cmd.output() { |
713 | Ok(output) => { | |
714 | if output.status.success() { | |
f8611cf8 | 715 | Ok(output.stdout) |
d76f7d0b XL |
716 | } else { |
717 | Err(Error::Failure { | |
718 | command: format!("{:?}", cmd), | |
217acde9 | 719 | output, |
d76f7d0b XL |
720 | }) |
721 | } | |
722 | } | |
723 | Err(cause) => Err(Error::Command { | |
724 | command: format!("{:?}", cmd), | |
217acde9 | 725 | cause, |
d76f7d0b XL |
726 | }), |
727 | } | |
728 | } | |
f8611cf8 VK |
729 | |
730 | /// Split output produced by pkg-config --cflags and / or --libs into separate flags. | |
731 | /// | |
732 | /// Backslash in output is used to preserve literal meaning of following byte. Different words are | |
733 | /// separated by unescaped space. Other whitespace characters generally should not occur unescaped | |
734 | /// at all, apart from the newline at the end of output. For compatibility with what others | |
735 | /// consumers of pkg-config output would do in this scenario, they are used here for splitting as | |
736 | /// well. | |
737 | fn split_flags(output: &[u8]) -> Vec<String> { | |
738 | let mut word = Vec::new(); | |
739 | let mut words = Vec::new(); | |
740 | let mut escaped = false; | |
741 | ||
742 | for &b in output { | |
743 | match b { | |
744 | _ if escaped => { | |
745 | escaped = false; | |
746 | word.push(b); | |
747 | } | |
217acde9 | 748 | b'\\' => escaped = true, |
f8611cf8 VK |
749 | b'\t' | b'\n' | b'\r' | b' ' => { |
750 | if !word.is_empty() { | |
751 | words.push(String::from_utf8(word).unwrap()); | |
752 | word = Vec::new(); | |
753 | } | |
754 | } | |
755 | _ => word.push(b), | |
756 | } | |
757 | } | |
758 | ||
759 | if !word.is_empty() { | |
760 | words.push(String::from_utf8(word).unwrap()); | |
761 | } | |
762 | ||
763 | words | |
764 | } | |
765 | ||
766 | #[test] | |
767 | #[cfg(target_os = "macos")] | |
768 | fn system_library_mac_test() { | |
c8d4b494 XL |
769 | use std::path::Path; |
770 | ||
771 | let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")]; | |
772 | ||
217acde9 XL |
773 | assert!(!is_static_available( |
774 | "PluginManager", | |
c8d4b494 | 775 | &system_roots, |
217acde9 XL |
776 | &[PathBuf::from("/Library/Frameworks")] |
777 | )); | |
778 | assert!(!is_static_available( | |
779 | "python2.7", | |
c8d4b494 | 780 | &system_roots, |
217acde9 XL |
781 | &[PathBuf::from( |
782 | "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config" | |
783 | )] | |
784 | )); | |
785 | assert!(!is_static_available( | |
786 | "ffi_convenience", | |
c8d4b494 | 787 | &system_roots, |
217acde9 XL |
788 | &[PathBuf::from( |
789 | "/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs" | |
790 | )] | |
791 | )); | |
f8611cf8 VK |
792 | |
793 | // Homebrew is in /usr/local, and it's not a part of the OS | |
794 | if Path::new("/usr/local/lib/libpng16.a").exists() { | |
217acde9 XL |
795 | assert!(is_static_available( |
796 | "png16", | |
c8d4b494 | 797 | &system_roots, |
217acde9 XL |
798 | &[PathBuf::from("/usr/local/lib")] |
799 | )); | |
800 | ||
801 | let libpng = Config::new() | |
802 | .range_version("1".."99") | |
803 | .probe("libpng16") | |
804 | .unwrap(); | |
805 | assert!(libpng.version.find('\n').is_none()); | |
f8611cf8 VK |
806 | } |
807 | } | |
808 | ||
809 | #[test] | |
810 | #[cfg(target_os = "linux")] | |
811 | fn system_library_linux_test() { | |
217acde9 XL |
812 | assert!(!is_static_available( |
813 | "util", | |
c8d4b494 | 814 | &[PathBuf::from("/usr")], |
217acde9 XL |
815 | &[PathBuf::from("/usr/lib/x86_64-linux-gnu")] |
816 | )); | |
c8d4b494 XL |
817 | assert!(!is_static_available( |
818 | "dialog", | |
819 | &[PathBuf::from("/usr")], | |
820 | &[PathBuf::from("/usr/lib")] | |
821 | )); | |
f8611cf8 | 822 | } |