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//! * `FOO_NO_PKG_CONFIG` - if set, this will disable running `pkg-config` when
13//! probing for the library named `foo`.
14//!
15//! * `PKG_CONFIG_ALLOW_CROSS` - The `pkg-config` command usually doesn't
16//! support cross-compilation, and this crate prevents it from selecting
17//! incompatible versions of libraries.
18//! Setting `PKG_CONFIG_ALLOW_CROSS=1` disables this protection, which is
19//! likely to cause linking errors, unless `pkg-config` has been configured
20//! to use appropriate sysroot and search paths for the target platform.
21//!
22//! There are also a number of environment variables which can configure how a
23//! library is linked to (dynamically vs statically). These variables control
24//! whether the `--static` flag is passed. Note that this behavior can be
25//! overridden by configuring explicitly on `Config`. The variables are checked
26//! in the following order:
27//!
28//! * `FOO_STATIC` - pass `--static` for the library `foo`
29//! * `FOO_DYNAMIC` - do not pass `--static` for the library `foo`
30//! * `PKG_CONFIG_ALL_STATIC` - pass `--static` for all libraries
31//! * `PKG_CONFIG_ALL_DYNAMIC` - do not pass `--static` for all libraries
32//!
33//! After running `pkg-config` all appropriate Cargo metadata will be printed on
34//! stdout if the search was successful.
35//!
36//! # Example
37//!
38//! Find the system library named `foo`, with minimum version 1.2.3:
39//!
40//! ```no_run
41//! fn main() {
42//! pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
43//! }
44//! ```
45//!
46//! Find the system library named `foo`, with no version requirement (not
47//! recommended):
48//!
49//! ```no_run
50//! fn main() {
51//! pkg_config::probe_library("foo").unwrap();
52//! }
53//! ```
54//!
55//! Configure how library `foo` is linked to.
56//!
57//! ```no_run
58//! fn main() {
59//! pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
60//! }
61//! ```
62
63#![doc(html_root_url = "https://docs.rs/pkg-config/0.3")]
64
65use std::collections::HashMap;
66use std::env;
67use std::error;
68use std::ffi::{OsStr, OsString};
69use std::fmt;
70use std::io;
71use std::ops::{Bound, RangeBounds};
72use std::path::PathBuf;
73use std::process::{Command, Output};
74use std::str;
75
76#[derive(Clone, Debug)]
77pub struct Config {
78 statik: Option<bool>,
79 min_version: Bound<String>,
80 max_version: Bound<String>,
81 extra_args: Vec<OsString>,
82 cargo_metadata: bool,
83 env_metadata: bool,
84 print_system_libs: bool,
85 print_system_cflags: bool,
86}
87
88#[derive(Clone, Debug)]
89pub struct Library {
90 /// Libraries specified by -l
91 pub libs: Vec<String>,
92 /// Library search paths specified by -L
93 pub link_paths: Vec<PathBuf>,
94 /// Library file paths specified without -l
95 pub link_files: Vec<PathBuf>,
96 /// Darwin frameworks specified by -framework
97 pub frameworks: Vec<String>,
98 /// Darwin framework search paths specified by -F
99 pub framework_paths: Vec<PathBuf>,
100 /// C/C++ header include paths specified by -I
101 pub include_paths: Vec<PathBuf>,
102 /// Linker options specified by -Wl
103 pub ld_args: Vec<Vec<String>>,
104 /// C/C++ definitions specified by -D
105 pub defines: HashMap<String, Option<String>>,
106 /// Version specified by .pc file's Version field
107 pub version: String,
108 /// Ensure that this struct can only be created via its private `[Library::new]` constructor.
109 /// Users of this crate can only access the struct via `[Config::probe]`.
110 _priv: (),
111}
112
113/// Represents all reasons `pkg-config` might not succeed or be run at all.
114pub enum Error {
115 /// Aborted because of `*_NO_PKG_CONFIG` environment variable.
116 ///
117 /// Contains the name of the responsible environment variable.
118 EnvNoPkgConfig(String),
119
120 /// Detected cross compilation without a custom sysroot.
121 ///
122 /// Ignore the error with `PKG_CONFIG_ALLOW_CROSS=1`,
123 /// which may let `pkg-config` select libraries
124 /// for the host's architecture instead of the target's.
125 CrossCompilation,
126
127 /// Failed to run `pkg-config`.
128 ///
129 /// Contains the command and the cause.
130 Command { command: String, cause: io::Error },
131
132 /// `pkg-config` did not exit sucessfully after probing a library.
133 ///
134 /// Contains the command and output.
135 Failure { command: String, output: Output },
136
137 /// `pkg-config` did not exit sucessfully on the first attempt to probe a library.
138 ///
139 /// Contains the command and output.
140 ProbeFailure {
141 name: String,
142 command: String,
143 output: Output,
144 },
145
146 #[doc(hidden)]
147 // please don't match on this, we're likely to add more variants over time
148 __Nonexhaustive,
149}
150
151impl error::Error for Error {}
152
153impl fmt::Debug for Error {
154 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
155 // Failed `unwrap()` prints Debug representation, but the default debug format lacks helpful instructions for the end users
156 <Error as fmt::Display>::fmt(self, f)
157 }
158}
159
160impl fmt::Display for Error {
161 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
162 match *self {
163 Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name),
164 Error::CrossCompilation => f.write_str(
165 "pkg-config has not been configured to support cross-compilation.\n\
166 \n\
167 Install a sysroot for the target platform and configure it via\n\
168 PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a\n\
169 cross-compiling wrapper for pkg-config and set it via\n\
170 PKG_CONFIG environment variable.",
171 ),
172 Error::Command {
173 ref command,
174 ref cause,
175 } => {
176 match cause.kind() {
177 io::ErrorKind::NotFound => {
178 let crate_name =
179 std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "sys".to_owned());
180 let instructions = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
181 "Try `brew install pkg-config` if you have Homebrew.\n"
182 } else if cfg!(unix) {
183 "Try `apt install pkg-config`, or `yum install pkg-config`,\n\
184 or `pkg install pkg-config`, or `apk add pkgconfig` \
185 depending on your distribution.\n"
186 } else {
187 "" // There's no easy fix for Windows users
188 };
189 write!(f, "Could not run `{command}`\n\
190 The pkg-config command could not be found.\n\
191 \n\
192 Most likely, you need to install a pkg-config package for your OS.\n\
193 {instructions}\
194 \n\
195 If you've already installed it, ensure the pkg-config command is one of the\n\
196 directories in the PATH environment variable.\n\
197 \n\
198 If you did not expect this build to link to a pre-installed system library,\n\
199 then check documentation of the {crate_name} crate for an option to\n\
200 build the library from source, or disable features or dependencies\n\
201 that require pkg-config.", command = command, instructions = instructions, crate_name = crate_name)
202 }
203 _ => write!(f, "Failed to run command `{}`, because: {}", command, cause),
204 }
205 }
206 Error::ProbeFailure {
207 ref name,
208 ref command,
209 ref output,
210 } => {
211 write!(
212 f,
213 "`{}` did not exit successfully: {}\nerror: could not find system library '{}' required by the '{}' crate\n",
214 command, output.status, name, env::var("CARGO_PKG_NAME").unwrap_or_default(),
215 )?;
216 format_output(output, f)
217 }
218 Error::Failure {
219 ref command,
220 ref output,
221 } => {
222 write!(
223 f,
224 "`{}` did not exit successfully: {}",
225 command, output.status
226 )?;
227 format_output(output, f)
228 }
229 Error::__Nonexhaustive => panic!(),
230 }
231 }
232}
233
234fn format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result {
235 let stdout: Cow<'_, str> = String::from_utf8_lossy(&output.stdout);
236 if !stdout.is_empty() {
237 write!(f, "\n--- stdout\n{}", stdout)?;
238 }
239 let stderr: Cow<'_, str> = String::from_utf8_lossy(&output.stderr);
240 if !stderr.is_empty() {
241 write!(f, "\n--- stderr\n{}", stderr)?;
242 }
243 Ok(())
244}
245
246/// Deprecated in favor of the probe_library function
247#[doc(hidden)]
248pub fn find_library(name: &str) -> Result<Library, String> {
249 probe_library(name).map_err(|e: Error| e.to_string())
250}
251
252/// Simple shortcut for using all default options for finding a library.
253pub fn probe_library(name: &str) -> Result<Library, Error> {
254 Config::new().probe(name)
255}
256
257#[doc(hidden)]
258#[deprecated(note = "use config.target_supported() instance method instead")]
259pub fn target_supported() -> bool {
260 Config::new().target_supported()
261}
262
263/// Run `pkg-config` to get the value of a variable from a package using
264/// `--variable`.
265///
266/// The content of `PKG_CONFIG_SYSROOT_DIR` is not injected in paths that are
267/// returned by `pkg-config --variable`, which makes them unsuitable to use
268/// during cross-compilation unless specifically designed to be used
269/// at that time.
270pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> {
271 let arg: String = format!("--variable={}", variable);
272 let cfg: Config = Config::new();
273 let out: Vec = cfg.run(name:package, &[&arg])?;
274 Ok(str::from_utf8(&out).unwrap().trim_end().to_owned())
275}
276
277impl Config {
278 /// Creates a new set of configuration options which are all initially set
279 /// to "blank".
280 pub fn new() -> Config {
281 Config {
282 statik: None,
283 min_version: Bound::Unbounded,
284 max_version: Bound::Unbounded,
285 extra_args: vec![],
286 print_system_cflags: true,
287 print_system_libs: true,
288 cargo_metadata: true,
289 env_metadata: true,
290 }
291 }
292
293 /// Indicate whether the `--static` flag should be passed.
294 ///
295 /// This will override the inference from environment variables described in
296 /// the crate documentation.
297 pub fn statik(&mut self, statik: bool) -> &mut Config {
298 self.statik = Some(statik);
299 self
300 }
301
302 /// Indicate that the library must be at least version `vers`.
303 pub fn atleast_version(&mut self, vers: &str) -> &mut Config {
304 self.min_version = Bound::Included(vers.to_string());
305 self.max_version = Bound::Unbounded;
306 self
307 }
308
309 /// Indicate that the library must be equal to version `vers`.
310 pub fn exactly_version(&mut self, vers: &str) -> &mut Config {
311 self.min_version = Bound::Included(vers.to_string());
312 self.max_version = Bound::Included(vers.to_string());
313 self
314 }
315
316 /// Indicate that the library's version must be in `range`.
317 pub fn range_version<'a, R>(&mut self, range: R) -> &mut Config
318 where
319 R: RangeBounds<&'a str>,
320 {
321 self.min_version = match range.start_bound() {
322 Bound::Included(vers) => Bound::Included(vers.to_string()),
323 Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
324 Bound::Unbounded => Bound::Unbounded,
325 };
326 self.max_version = match range.end_bound() {
327 Bound::Included(vers) => Bound::Included(vers.to_string()),
328 Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
329 Bound::Unbounded => Bound::Unbounded,
330 };
331 self
332 }
333
334 /// Add an argument to pass to pkg-config.
335 ///
336 /// It's placed after all of the arguments generated by this library.
337 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config {
338 self.extra_args.push(arg.as_ref().to_os_string());
339 self
340 }
341
342 /// Define whether metadata should be emitted for cargo allowing it to
343 /// automatically link the binary. Defaults to `true`.
344 pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
345 self.cargo_metadata = cargo_metadata;
346 self
347 }
348
349 /// Define whether metadata should be emitted for cargo allowing to
350 /// automatically rebuild when environment variables change. Defaults to
351 /// `true`.
352 pub fn env_metadata(&mut self, env_metadata: bool) -> &mut Config {
353 self.env_metadata = env_metadata;
354 self
355 }
356
357 /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_LIBS` environment
358 /// variable.
359 ///
360 /// This env var is enabled by default.
361 pub fn print_system_libs(&mut self, print: bool) -> &mut Config {
362 self.print_system_libs = print;
363 self
364 }
365
366 /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS` environment
367 /// variable.
368 ///
369 /// This env var is enabled by default.
370 pub fn print_system_cflags(&mut self, print: bool) -> &mut Config {
371 self.print_system_cflags = print;
372 self
373 }
374
375 /// Deprecated in favor fo the `probe` function
376 #[doc(hidden)]
377 pub fn find(&self, name: &str) -> Result<Library, String> {
378 self.probe(name).map_err(|e| e.to_string())
379 }
380
381 /// Run `pkg-config` to find the library `name`.
382 ///
383 /// This will use all configuration previously set to specify how
384 /// `pkg-config` is run.
385 pub fn probe(&self, name: &str) -> Result<Library, Error> {
386 let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name));
387 if self.env_var_os(&abort_var_name).is_some() {
388 return Err(Error::EnvNoPkgConfig(abort_var_name));
389 } else if !self.target_supported() {
390 return Err(Error::CrossCompilation);
391 }
392
393 let mut library = Library::new();
394
395 let output = self
396 .run(name, &["--libs", "--cflags"])
397 .map_err(|e| match e {
398 Error::Failure { command, output } => Error::ProbeFailure {
399 name: name.to_owned(),
400 command,
401 output,
402 },
403 other => other,
404 })?;
405 library.parse_libs_cflags(name, &output, self);
406
407 let output = self.run(name, &["--modversion"])?;
408 library.parse_modversion(str::from_utf8(&output).unwrap());
409
410 Ok(library)
411 }
412
413 /// True if pkg-config is used for the host system, or configured for cross-compilation
414 pub fn target_supported(&self) -> bool {
415 let target = env::var_os("TARGET").unwrap_or_default();
416 let host = env::var_os("HOST").unwrap_or_default();
417
418 // Only use pkg-config in host == target situations by default (allowing an
419 // override).
420 if host == target {
421 return true;
422 }
423
424 // pkg-config may not be aware of cross-compilation, and require
425 // a wrapper script that sets up platform-specific prefixes.
426 match self.targetted_env_var("PKG_CONFIG_ALLOW_CROSS") {
427 // don't use pkg-config if explicitly disabled
428 Some(ref val) if val == "0" => false,
429 Some(_) => true,
430 None => {
431 // if not disabled, and pkg-config is customized,
432 // then assume it's prepared for cross-compilation
433 self.targetted_env_var("PKG_CONFIG").is_some()
434 || self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
435 }
436 }
437 }
438
439 /// Deprecated in favor of the top level `get_variable` function
440 #[doc(hidden)]
441 pub fn get_variable(package: &str, variable: &str) -> Result<String, String> {
442 get_variable(package, variable).map_err(|e| e.to_string())
443 }
444
445 fn targetted_env_var(&self, var_base: &str) -> Option<OsString> {
446 match (env::var("TARGET"), env::var("HOST")) {
447 (Ok(target), Ok(host)) => {
448 let kind = if host == target { "HOST" } else { "TARGET" };
449 let target_u = target.replace('-', "_");
450
451 self.env_var_os(&format!("{}_{}", var_base, target))
452 .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u)))
453 .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base)))
454 .or_else(|| self.env_var_os(var_base))
455 }
456 (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => {
457 self.env_var_os(var_base)
458 }
459 (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => {
460 panic!(
461 "HOST or TARGET environment variable is not valid unicode: {:?}",
462 s
463 )
464 }
465 }
466 }
467
468 fn env_var_os(&self, name: &str) -> Option<OsString> {
469 if self.env_metadata {
470 println!("cargo:rerun-if-env-changed={}", name);
471 }
472 env::var_os(name)
473 }
474
475 fn is_static(&self, name: &str) -> bool {
476 self.statik.unwrap_or_else(|| self.infer_static(name))
477 }
478
479 fn run(&self, name: &str, args: &[&str]) -> Result<Vec<u8>, Error> {
480 let pkg_config_exe = self.targetted_env_var("PKG_CONFIG");
481 let fallback_exe = if pkg_config_exe.is_none() {
482 Some(OsString::from("pkgconf"))
483 } else {
484 None
485 };
486 let exe = pkg_config_exe.unwrap_or_else(|| OsString::from("pkg-config"));
487
488 let mut cmd = self.command(exe, name, args);
489
490 match cmd.output().or_else(|e| {
491 if let Some(exe) = fallback_exe {
492 self.command(exe, name, args).output()
493 } else {
494 Err(e)
495 }
496 }) {
497 Ok(output) => {
498 if output.status.success() {
499 Ok(output.stdout)
500 } else {
501 Err(Error::Failure {
502 command: format!("{:?}", cmd),
503 output,
504 })
505 }
506 }
507 Err(cause) => Err(Error::Command {
508 command: format!("{:?}", cmd),
509 cause,
510 }),
511 }
512 }
513
514 fn command(&self, exe: OsString, name: &str, args: &[&str]) -> Command {
515 let mut cmd = Command::new(exe);
516 if self.is_static(name) {
517 cmd.arg("--static");
518 }
519 cmd.args(args).args(&self.extra_args);
520
521 if let Some(value) = self.targetted_env_var("PKG_CONFIG_PATH") {
522 cmd.env("PKG_CONFIG_PATH", value);
523 }
524 if let Some(value) = self.targetted_env_var("PKG_CONFIG_LIBDIR") {
525 cmd.env("PKG_CONFIG_LIBDIR", value);
526 }
527 if let Some(value) = self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR") {
528 cmd.env("PKG_CONFIG_SYSROOT_DIR", value);
529 }
530 if self.print_system_libs {
531 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
532 }
533 if self.print_system_cflags {
534 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1");
535 }
536 cmd.arg(name);
537 match self.min_version {
538 Bound::Included(ref version) => {
539 cmd.arg(&format!("{} >= {}", name, version));
540 }
541 Bound::Excluded(ref version) => {
542 cmd.arg(&format!("{} > {}", name, version));
543 }
544 _ => (),
545 }
546 match self.max_version {
547 Bound::Included(ref version) => {
548 cmd.arg(&format!("{} <= {}", name, version));
549 }
550 Bound::Excluded(ref version) => {
551 cmd.arg(&format!("{} < {}", name, version));
552 }
553 _ => (),
554 }
555 cmd
556 }
557
558 fn print_metadata(&self, s: &str) {
559 if self.cargo_metadata {
560 println!("cargo:{}", s);
561 }
562 }
563
564 fn infer_static(&self, name: &str) -> bool {
565 let name = envify(name);
566 if self.env_var_os(&format!("{}_STATIC", name)).is_some() {
567 true
568 } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() {
569 false
570 } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() {
571 true
572 } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
573 false
574 } else {
575 false
576 }
577 }
578}
579
580// Implement Default manualy since Bound does not implement Default.
581impl Default for Config {
582 fn default() -> Config {
583 Config {
584 statik: None,
585 min_version: Bound::Unbounded,
586 max_version: Bound::Unbounded,
587 extra_args: vec![],
588 print_system_cflags: false,
589 print_system_libs: false,
590 cargo_metadata: false,
591 env_metadata: false,
592 }
593 }
594}
595
596impl Library {
597 fn new() -> Library {
598 Library {
599 libs: Vec::new(),
600 link_paths: Vec::new(),
601 link_files: Vec::new(),
602 include_paths: Vec::new(),
603 ld_args: Vec::new(),
604 frameworks: Vec::new(),
605 framework_paths: Vec::new(),
606 defines: HashMap::new(),
607 version: String::new(),
608 _priv: (),
609 }
610 }
611
612 /// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
613 /// using target-specific logic.
614 fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
615 fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
616 for suffix in suffixes {
617 if filename.ends_with(suffix) {
618 return Some(&filename[..filename.len() - suffix.len()]);
619 }
620 }
621 None
622 }
623
624 let prefix = "lib";
625 if target.contains("msvc") {
626 // According to link.exe documentation:
627 // https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
628 //
629 // LINK doesn't use file extensions to make assumptions about the contents of a file.
630 // Instead, LINK examines each input file to determine what kind of file it is.
631 //
632 // However, rustc appends `.lib` to the string it receives from the -l command line argument,
633 // which it receives from Cargo via cargo:rustc-link-lib:
634 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
635 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
636 // So the only file extension that works for MSVC targets is `.lib`
637 return test_suffixes(filename, &[".lib"]);
638 } else if target.contains("windows") && target.contains("gnu") {
639 // GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
640 // which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
641 // receives via the -l command line argument before passing it to the linker:
642 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
643 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
644 // GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
645 // GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
646 // the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
647 // https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
648 if filename.starts_with(prefix) {
649 let filename = &filename[prefix.len()..];
650 return test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]);
651 } else {
652 return test_suffixes(filename, &[".dll.a", ".dll", ".lib"]);
653 }
654 } else if target.contains("apple") {
655 if filename.starts_with(prefix) {
656 let filename = &filename[prefix.len()..];
657 return test_suffixes(filename, &[".a", ".so", ".dylib"]);
658 }
659 return None;
660 } else {
661 if filename.starts_with(prefix) {
662 let filename = &filename[prefix.len()..];
663 return test_suffixes(filename, &[".a", ".so"]);
664 }
665 return None;
666 }
667 }
668
669 fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
670 let mut is_msvc = false;
671 let target = env::var("TARGET");
672 if let Ok(target) = &target {
673 if target.contains("msvc") {
674 is_msvc = true;
675 }
676 }
677
678 let system_roots = if cfg!(target_os = "macos") {
679 vec![PathBuf::from("/Library"), PathBuf::from("/System")]
680 } else {
681 let sysroot = config
682 .env_var_os("PKG_CONFIG_SYSROOT_DIR")
683 .or_else(|| config.env_var_os("SYSROOT"))
684 .map(PathBuf::from);
685
686 if cfg!(target_os = "windows") {
687 if let Some(sysroot) = sysroot {
688 vec![sysroot]
689 } else {
690 vec![]
691 }
692 } else {
693 vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
694 }
695 };
696
697 let mut dirs = Vec::new();
698 let statik = config.is_static(name);
699
700 let words = split_flags(output);
701
702 // Handle single-character arguments like `-I/usr/include`
703 let parts = words
704 .iter()
705 .filter(|l| l.len() > 2)
706 .map(|arg| (&arg[0..2], &arg[2..]));
707 for (flag, val) in parts {
708 match flag {
709 "-L" => {
710 let meta = format!("rustc-link-search=native={}", val);
711 config.print_metadata(&meta);
712 dirs.push(PathBuf::from(val));
713 self.link_paths.push(PathBuf::from(val));
714 }
715 "-F" => {
716 let meta = format!("rustc-link-search=framework={}", val);
717 config.print_metadata(&meta);
718 self.framework_paths.push(PathBuf::from(val));
719 }
720 "-I" => {
721 self.include_paths.push(PathBuf::from(val));
722 }
723 "-l" => {
724 // These are provided by the CRT with MSVC
725 if is_msvc && ["m", "c", "pthread"].contains(&val) {
726 continue;
727 }
728
729 if statik && is_static_available(val, &system_roots, &dirs) {
730 let meta = format!("rustc-link-lib=static={}", val);
731 config.print_metadata(&meta);
732 } else {
733 let meta = format!("rustc-link-lib={}", val);
734 config.print_metadata(&meta);
735 }
736
737 self.libs.push(val.to_string());
738 }
739 "-D" => {
740 let mut iter = val.split('=');
741 self.defines.insert(
742 iter.next().unwrap().to_owned(),
743 iter.next().map(|s| s.to_owned()),
744 );
745 }
746 _ => {}
747 }
748 }
749
750 // Handle multi-character arguments with space-separated value like `-framework foo`
751 let mut iter = words.iter().flat_map(|arg| {
752 if arg.starts_with("-Wl,") {
753 arg[4..].split(',').collect()
754 } else {
755 vec![arg.as_ref()]
756 }
757 });
758 while let Some(part) = iter.next() {
759 match part {
760 "-framework" => {
761 if let Some(lib) = iter.next() {
762 let meta = format!("rustc-link-lib=framework={}", lib);
763 config.print_metadata(&meta);
764 self.frameworks.push(lib.to_string());
765 }
766 }
767 "-isystem" | "-iquote" | "-idirafter" => {
768 if let Some(inc) = iter.next() {
769 self.include_paths.push(PathBuf::from(inc));
770 }
771 }
772 _ => {
773 let path = std::path::Path::new(part);
774 if path.is_file() {
775 // Cargo doesn't have a means to directly specify a file path to link,
776 // so split up the path into the parent directory and library name.
777 // TODO: pass file path directly when link-arg library type is stabilized
778 // https://github.com/rust-lang/rust/issues/99427
779 if let (Some(dir), Some(file_name), Ok(target)) =
780 (path.parent(), path.file_name(), &target)
781 {
782 match Self::extract_lib_from_filename(
783 target,
784 &file_name.to_string_lossy(),
785 ) {
786 Some(lib_basename) => {
787 let link_search =
788 format!("rustc-link-search={}", dir.display());
789 config.print_metadata(&link_search);
790
791 let link_lib = format!("rustc-link-lib={}", lib_basename);
792 config.print_metadata(&link_lib);
793 self.link_files.push(PathBuf::from(path));
794 }
795 None => {
796 println!("cargo:warning=File path {} found in pkg-config file for {}, but could not extract library base name to pass to linker command line", path.display(), name);
797 }
798 }
799 }
800 }
801 }
802 }
803 }
804
805 let linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,"));
806 for option in linker_options {
807 let mut pop = false;
808 let mut ld_option = vec![];
809 for subopt in option[4..].split(',') {
810 if pop {
811 pop = false;
812 continue;
813 }
814
815 if subopt == "-framework" {
816 pop = true;
817 continue;
818 }
819
820 ld_option.push(subopt);
821 }
822
823 let meta = format!("rustc-link-arg=-Wl,{}", ld_option.join(","));
824 config.print_metadata(&meta);
825
826 self.ld_args
827 .push(ld_option.into_iter().map(String::from).collect());
828 }
829 }
830
831 fn parse_modversion(&mut self, output: &str) {
832 self.version.push_str(output.lines().next().unwrap().trim());
833 }
834}
835
836fn envify(name: &str) -> String {
837 nameimpl Iterator.chars()
838 .map(|c: char| c.to_ascii_uppercase())
839 .map(|c: char| if c == '-' { '_' } else { c })
840 .collect()
841}
842
843/// System libraries should only be linked dynamically
844fn is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool {
845 let libname: String = format!("lib{}.a", name);
846
847 dirs.iter().any(|dir: &PathBuf| {
848 !system_roots.iter().any(|sys: &PathBuf| dir.starts_with(base:sys)) && dir.join(&libname).exists()
849 })
850}
851
852/// Split output produced by pkg-config --cflags and / or --libs into separate flags.
853///
854/// Backslash in output is used to preserve literal meaning of following byte. Different words are
855/// separated by unescaped space. Other whitespace characters generally should not occur unescaped
856/// at all, apart from the newline at the end of output. For compatibility with what others
857/// consumers of pkg-config output would do in this scenario, they are used here for splitting as
858/// well.
859fn split_flags(output: &[u8]) -> Vec<String> {
860 let mut word = Vec::new();
861 let mut words = Vec::new();
862 let mut escaped = false;
863
864 for &b in output {
865 match b {
866 _ if escaped => {
867 escaped = false;
868 word.push(b);
869 }
870 b'\\' => escaped = true,
871 b'\t' | b'\n' | b'\r' | b' ' => {
872 if !word.is_empty() {
873 words.push(String::from_utf8(word).unwrap());
874 word = Vec::new();
875 }
876 }
877 _ => word.push(b),
878 }
879 }
880
881 if !word.is_empty() {
882 words.push(String::from_utf8(word).unwrap());
883 }
884
885 words
886}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 #[test]
893 #[cfg(target_os = "macos")]
894 fn system_library_mac_test() {
895 use std::path::Path;
896
897 let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
898
899 assert!(!is_static_available(
900 "PluginManager",
901 &system_roots,
902 &[PathBuf::from("/Library/Frameworks")]
903 ));
904 assert!(!is_static_available(
905 "python2.7",
906 &system_roots,
907 &[PathBuf::from(
908 "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
909 )]
910 ));
911 assert!(!is_static_available(
912 "ffi_convenience",
913 &system_roots,
914 &[PathBuf::from(
915 "/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
916 )]
917 ));
918
919 // Homebrew is in /usr/local, and it's not a part of the OS
920 if Path::new("/usr/local/lib/libpng16.a").exists() {
921 assert!(is_static_available(
922 "png16",
923 &system_roots,
924 &[PathBuf::from("/usr/local/lib")]
925 ));
926
927 let libpng = Config::new()
928 .range_version("1".."99")
929 .probe("libpng16")
930 .unwrap();
931 assert!(libpng.version.find('\n').is_none());
932 }
933 }
934
935 #[test]
936 #[cfg(target_os = "linux")]
937 fn system_library_linux_test() {
938 assert!(!is_static_available(
939 "util",
940 &[PathBuf::from("/usr")],
941 &[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
942 ));
943 assert!(!is_static_available(
944 "dialog",
945 &[PathBuf::from("/usr")],
946 &[PathBuf::from("/usr/lib")]
947 ));
948 }
949
950 fn test_library_filename(target: &str, filename: &str) {
951 assert_eq!(
952 Library::extract_lib_from_filename(target, filename),
953 Some("foo")
954 );
955 }
956
957 #[test]
958 fn link_filename_linux() {
959 let target = "x86_64-unknown-linux-gnu";
960 test_library_filename(target, "libfoo.a");
961 test_library_filename(target, "libfoo.so");
962 }
963
964 #[test]
965 fn link_filename_apple() {
966 let target = "x86_64-apple-darwin";
967 test_library_filename(target, "libfoo.a");
968 test_library_filename(target, "libfoo.so");
969 test_library_filename(target, "libfoo.dylib");
970 }
971
972 #[test]
973 fn link_filename_msvc() {
974 let target = "x86_64-pc-windows-msvc";
975 // static and dynamic libraries have the same .lib suffix
976 test_library_filename(target, "foo.lib");
977 }
978
979 #[test]
980 fn link_filename_mingw() {
981 let target = "x86_64-pc-windows-gnu";
982 test_library_filename(target, "foo.lib");
983 test_library_filename(target, "libfoo.a");
984 test_library_filename(target, "foo.dll");
985 test_library_filename(target, "foo.dll.a");
986 }
987}
988