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
65 use std::collections::HashMap;
66 use std::env;
67 use std::error;
68 use std::ffi::{OsStr, OsString};
69 use std::fmt;
70 use std::io;
71 use std::ops::{Bound, RangeBounds};
72 use std::path::PathBuf;
73 use std::process::{Command, Output};
74 use std::str;
75
76 #[derive(Clone, Debug)]
77 pub 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)]
89 pub 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.
114 pub 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 successfully after probing a library.
133 ///
134 /// Contains the command and output.
135 Failure { command: String, output: Output },
136
137 /// `pkg-config` did not exit successfully 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
151 impl error::Error for Error {}
152
153 impl fmt::Debug for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::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
160 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::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
format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result234 fn format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result {
235 let stdout = String::from_utf8_lossy(&output.stdout);
236 if !stdout.is_empty() {
237 write!(f, "\n--- stdout\n{}", stdout)?;
238 }
239 let stderr = 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)]
find_library(name: &str) -> Result<Library, String>248 pub fn find_library(name: &str) -> Result<Library, String> {
249 probe_library(name).map_err(|e| e.to_string())
250 }
251
252 /// Simple shortcut for using all default options for finding a library.
probe_library(name: &str) -> Result<Library, Error>253 pub 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")]
target_supported() -> bool259 pub 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.
get_variable(package: &str, variable: &str) -> Result<String, Error>270 pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> {
271 let arg = format!("--variable={}", variable);
272 let cfg = Config::new();
273 let out = run(cfg.command(package, &[&arg]))?;
274 Ok(str::from_utf8(&out).unwrap().trim_end().to_owned())
275 }
276
277 impl Config {
278 /// Creates a new set of configuration options which are all initially set
279 /// to "blank".
new() -> Config280 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.
statik(&mut self, statik: bool) -> &mut Config297 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`.
atleast_version(&mut self, vers: &str) -> &mut Config303 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`.
exactly_version(&mut self, vers: &str) -> &mut Config310 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`.
range_version<'a, R>(&mut self, range: R) -> &mut Config where R: RangeBounds<&'a str>,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.
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config337 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`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config344 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`.
env_metadata(&mut self, env_metadata: bool) -> &mut Config352 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.
print_system_libs(&mut self, print: bool) -> &mut Config361 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.
print_system_cflags(&mut self, print: bool) -> &mut Config370 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)]
find(&self, name: &str) -> Result<Library, String>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.
probe(&self, name: &str) -> Result<Library, Error>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 = run(self.command(name, &["--libs", "--cflags"])).map_err(|e| match e {
396 Error::Failure { command, output } => Error::ProbeFailure {
397 name: name.to_owned(),
398 command,
399 output,
400 },
401 other => other,
402 })?;
403 library.parse_libs_cflags(name, &output, self);
404
405 let output = run(self.command(name, &["--modversion"]))?;
406 library.parse_modversion(str::from_utf8(&output).unwrap());
407
408 Ok(library)
409 }
410
411 /// True if pkg-config is used for the host system, or configured for cross-compilation
target_supported(&self) -> bool412 pub fn target_supported(&self) -> bool {
413 let target = env::var_os("TARGET").unwrap_or_default();
414 let host = env::var_os("HOST").unwrap_or_default();
415
416 // Only use pkg-config in host == target situations by default (allowing an
417 // override).
418 if host == target {
419 return true;
420 }
421
422 // pkg-config may not be aware of cross-compilation, and require
423 // a wrapper script that sets up platform-specific prefixes.
424 match self.targeted_env_var("PKG_CONFIG_ALLOW_CROSS") {
425 // don't use pkg-config if explicitly disabled
426 Some(ref val) if val == "0" => false,
427 Some(_) => true,
428 None => {
429 // if not disabled, and pkg-config is customized,
430 // then assume it's prepared for cross-compilation
431 self.targeted_env_var("PKG_CONFIG").is_some()
432 || self.targeted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
433 }
434 }
435 }
436
437 /// Deprecated in favor of the top level `get_variable` function
438 #[doc(hidden)]
get_variable(package: &str, variable: &str) -> Result<String, String>439 pub fn get_variable(package: &str, variable: &str) -> Result<String, String> {
440 get_variable(package, variable).map_err(|e| e.to_string())
441 }
442
targeted_env_var(&self, var_base: &str) -> Option<OsString>443 fn targeted_env_var(&self, var_base: &str) -> Option<OsString> {
444 match (env::var("TARGET"), env::var("HOST")) {
445 (Ok(target), Ok(host)) => {
446 let kind = if host == target { "HOST" } else { "TARGET" };
447 let target_u = target.replace("-", "_");
448
449 self.env_var_os(&format!("{}_{}", var_base, target))
450 .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u)))
451 .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base)))
452 .or_else(|| self.env_var_os(var_base))
453 }
454 (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => {
455 self.env_var_os(var_base)
456 }
457 (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => {
458 panic!(
459 "HOST or TARGET environment variable is not valid unicode: {:?}",
460 s
461 )
462 }
463 }
464 }
465
env_var_os(&self, name: &str) -> Option<OsString>466 fn env_var_os(&self, name: &str) -> Option<OsString> {
467 if self.env_metadata {
468 println!("cargo:rerun-if-env-changed={}", name);
469 }
470 env::var_os(name)
471 }
472
is_static(&self, name: &str) -> bool473 fn is_static(&self, name: &str) -> bool {
474 self.statik.unwrap_or_else(|| self.infer_static(name))
475 }
476
command(&self, name: &str, args: &[&str]) -> Command477 fn command(&self, name: &str, args: &[&str]) -> Command {
478 let exe = self
479 .targeted_env_var("PKG_CONFIG")
480 .unwrap_or_else(|| OsString::from("pkg-config"));
481 let mut cmd = Command::new(exe);
482 if self.is_static(name) {
483 cmd.arg("--static");
484 }
485 cmd.args(args).args(&self.extra_args);
486
487 if let Some(value) = self.targeted_env_var("PKG_CONFIG_PATH") {
488 cmd.env("PKG_CONFIG_PATH", value);
489 }
490 if let Some(value) = self.targeted_env_var("PKG_CONFIG_LIBDIR") {
491 cmd.env("PKG_CONFIG_LIBDIR", value);
492 }
493 if let Some(value) = self.targeted_env_var("PKG_CONFIG_SYSROOT_DIR") {
494 cmd.env("PKG_CONFIG_SYSROOT_DIR", value);
495 }
496 if self.print_system_libs {
497 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
498 }
499 if self.print_system_cflags {
500 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1");
501 }
502 cmd.arg(name);
503 match self.min_version {
504 Bound::Included(ref version) => {
505 cmd.arg(&format!("{} >= {}", name, version));
506 }
507 Bound::Excluded(ref version) => {
508 cmd.arg(&format!("{} > {}", name, version));
509 }
510 _ => (),
511 }
512 match self.max_version {
513 Bound::Included(ref version) => {
514 cmd.arg(&format!("{} <= {}", name, version));
515 }
516 Bound::Excluded(ref version) => {
517 cmd.arg(&format!("{} < {}", name, version));
518 }
519 _ => (),
520 }
521 cmd
522 }
523
print_metadata(&self, s: &str)524 fn print_metadata(&self, s: &str) {
525 if self.cargo_metadata {
526 println!("cargo:{}", s);
527 }
528 }
529
infer_static(&self, name: &str) -> bool530 fn infer_static(&self, name: &str) -> bool {
531 let name = envify(name);
532 if self.env_var_os(&format!("{}_STATIC", name)).is_some() {
533 true
534 } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() {
535 false
536 } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() {
537 true
538 } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
539 false
540 } else {
541 false
542 }
543 }
544 }
545
546 // Implement Default manually since Bound does not implement Default.
547 impl Default for Config {
default() -> Config548 fn default() -> Config {
549 Config {
550 statik: None,
551 min_version: Bound::Unbounded,
552 max_version: Bound::Unbounded,
553 extra_args: vec![],
554 print_system_cflags: false,
555 print_system_libs: false,
556 cargo_metadata: false,
557 env_metadata: false,
558 }
559 }
560 }
561
562 impl Library {
new() -> Library563 fn new() -> Library {
564 Library {
565 libs: Vec::new(),
566 link_paths: Vec::new(),
567 link_files: Vec::new(),
568 include_paths: Vec::new(),
569 ld_args: Vec::new(),
570 frameworks: Vec::new(),
571 framework_paths: Vec::new(),
572 defines: HashMap::new(),
573 version: String::new(),
574 _priv: (),
575 }
576 }
577
578 /// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
579 /// using target-specific logic.
extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str>580 fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
581 fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
582 for suffix in suffixes {
583 if filename.ends_with(suffix) {
584 return Some(&filename[..filename.len() - suffix.len()]);
585 }
586 }
587 None
588 }
589
590 let prefix = "lib";
591 if target.contains("msvc") {
592 // According to link.exe documentation:
593 // https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
594 //
595 // LINK doesn't use file extensions to make assumptions about the contents of a file.
596 // Instead, LINK examines each input file to determine what kind of file it is.
597 //
598 // However, rustc appends `.lib` to the string it receives from the -l command line argument,
599 // which it receives from Cargo via cargo:rustc-link-lib:
600 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
601 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
602 // So the only file extension that works for MSVC targets is `.lib`
603 return test_suffixes(filename, &[".lib"]);
604 } else if target.contains("windows") && target.contains("gnu") {
605 // GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
606 // which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
607 // receives via the -l command line argument before passing it to the linker:
608 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
609 // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
610 // GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
611 // GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
612 // the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
613 // https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
614 if filename.starts_with(prefix) {
615 let filename = &filename[prefix.len()..];
616 return test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]);
617 } else {
618 return test_suffixes(filename, &[".dll.a", ".dll", ".lib"]);
619 }
620 } else if target.contains("apple") {
621 if filename.starts_with(prefix) {
622 let filename = &filename[prefix.len()..];
623 return test_suffixes(filename, &[".a", ".so", ".dylib"]);
624 }
625 return None;
626 } else {
627 if filename.starts_with(prefix) {
628 let filename = &filename[prefix.len()..];
629 return test_suffixes(filename, &[".a", ".so"]);
630 }
631 return None;
632 }
633 }
634
parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config)635 fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
636 let mut is_msvc = false;
637 let target = env::var("TARGET");
638 if let Ok(target) = &target {
639 if target.contains("msvc") {
640 is_msvc = true;
641 }
642 }
643
644 let system_roots = if cfg!(target_os = "macos") {
645 vec![PathBuf::from("/Library"), PathBuf::from("/System")]
646 } else {
647 let sysroot = config
648 .env_var_os("PKG_CONFIG_SYSROOT_DIR")
649 .or_else(|| config.env_var_os("SYSROOT"))
650 .map(PathBuf::from);
651
652 if cfg!(target_os = "windows") {
653 if let Some(sysroot) = sysroot {
654 vec![sysroot]
655 } else {
656 vec![]
657 }
658 } else {
659 vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
660 }
661 };
662
663 let mut dirs = Vec::new();
664 let statik = config.is_static(name);
665
666 let words = split_flags(output);
667
668 // Handle single-character arguments like `-I/usr/include`
669 let parts = words
670 .iter()
671 .filter(|l| l.len() > 2)
672 .map(|arg| (&arg[0..2], &arg[2..]));
673 for (flag, val) in parts {
674 match flag {
675 "-L" => {
676 let meta = format!("rustc-link-search=native={}", val);
677 config.print_metadata(&meta);
678 dirs.push(PathBuf::from(val));
679 self.link_paths.push(PathBuf::from(val));
680 }
681 "-F" => {
682 let meta = format!("rustc-link-search=framework={}", val);
683 config.print_metadata(&meta);
684 self.framework_paths.push(PathBuf::from(val));
685 }
686 "-I" => {
687 self.include_paths.push(PathBuf::from(val));
688 }
689 "-l" => {
690 // These are provided by the CRT with MSVC
691 if is_msvc && ["m", "c", "pthread"].contains(&val) {
692 continue;
693 }
694
695 if statik && is_static_available(val, &system_roots, &dirs) {
696 let meta = format!("rustc-link-lib=static={}", val);
697 config.print_metadata(&meta);
698 } else {
699 let meta = format!("rustc-link-lib={}", val);
700 config.print_metadata(&meta);
701 }
702
703 self.libs.push(val.to_string());
704 }
705 "-D" => {
706 let mut iter = val.split('=');
707 self.defines.insert(
708 iter.next().unwrap().to_owned(),
709 iter.next().map(|s| s.to_owned()),
710 );
711 }
712 _ => {}
713 }
714 }
715
716 // Handle multi-character arguments with space-separated value like `-framework foo`
717 let mut iter = words.iter().flat_map(|arg| {
718 if arg.starts_with("-Wl,") {
719 arg[4..].split(',').collect()
720 } else {
721 vec![arg.as_ref()]
722 }
723 });
724 while let Some(part) = iter.next() {
725 match part {
726 "-framework" => {
727 if let Some(lib) = iter.next() {
728 let meta = format!("rustc-link-lib=framework={}", lib);
729 config.print_metadata(&meta);
730 self.frameworks.push(lib.to_string());
731 }
732 }
733 "-isystem" | "-iquote" | "-idirafter" => {
734 if let Some(inc) = iter.next() {
735 self.include_paths.push(PathBuf::from(inc));
736 }
737 }
738 _ => {
739 let path = std::path::Path::new(part);
740 if path.is_file() {
741 // Cargo doesn't have a means to directly specify a file path to link,
742 // so split up the path into the parent directory and library name.
743 // TODO: pass file path directly when link-arg library type is stabilized
744 // https://github.com/rust-lang/rust/issues/99427
745 if let (Some(dir), Some(file_name), Ok(target)) =
746 (path.parent(), path.file_name(), &target)
747 {
748 match Self::extract_lib_from_filename(
749 target,
750 &file_name.to_string_lossy(),
751 ) {
752 Some(lib_basename) => {
753 let link_search =
754 format!("rustc-link-search={}", dir.display());
755 config.print_metadata(&link_search);
756
757 let link_lib = format!("rustc-link-lib={}", lib_basename);
758 config.print_metadata(&link_lib);
759 self.link_files.push(PathBuf::from(path));
760 }
761 None => {
762 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);
763 }
764 }
765 }
766 }
767 }
768 }
769 }
770
771 let mut linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,"));
772 while let Some(option) = linker_options.next() {
773 let mut pop = false;
774 let mut ld_option = vec![];
775 for subopt in option[4..].split(',') {
776 if pop {
777 pop = false;
778 continue;
779 }
780
781 if subopt == "-framework" {
782 pop = true;
783 continue;
784 }
785
786 ld_option.push(subopt);
787 }
788
789 let meta = format!("rustc-link-arg=-Wl,{}", ld_option.join(","));
790 config.print_metadata(&meta);
791
792 self.ld_args
793 .push(ld_option.into_iter().map(String::from).collect());
794 }
795 }
796
parse_modversion(&mut self, output: &str)797 fn parse_modversion(&mut self, output: &str) {
798 self.version.push_str(output.lines().nth(0).unwrap().trim());
799 }
800 }
801
envify(name: &str) -> String802 fn envify(name: &str) -> String {
803 name.chars()
804 .map(|c| c.to_ascii_uppercase())
805 .map(|c| if c == '-' { '_' } else { c })
806 .collect()
807 }
808
809 /// System libraries should only be linked dynamically
is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool810 fn is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool {
811 let libname = format!("lib{}.a", name);
812
813 dirs.iter().any(|dir| {
814 !system_roots.iter().any(|sys| dir.starts_with(sys)) && dir.join(&libname).exists()
815 })
816 }
817
run(mut cmd: Command) -> Result<Vec<u8>, Error>818 fn run(mut cmd: Command) -> Result<Vec<u8>, Error> {
819 match cmd.output() {
820 Ok(output) => {
821 if output.status.success() {
822 Ok(output.stdout)
823 } else {
824 Err(Error::Failure {
825 command: format!("{:?}", cmd),
826 output,
827 })
828 }
829 }
830 Err(cause) => Err(Error::Command {
831 command: format!("{:?}", cmd),
832 cause,
833 }),
834 }
835 }
836
837 /// Split output produced by pkg-config --cflags and / or --libs into separate flags.
838 ///
839 /// Backslash in output is used to preserve literal meaning of following byte. Different words are
840 /// separated by unescaped space. Other whitespace characters generally should not occur unescaped
841 /// at all, apart from the newline at the end of output. For compatibility with what others
842 /// consumers of pkg-config output would do in this scenario, they are used here for splitting as
843 /// well.
split_flags(output: &[u8]) -> Vec<String>844 fn split_flags(output: &[u8]) -> Vec<String> {
845 let mut word = Vec::new();
846 let mut words = Vec::new();
847 let mut escaped = false;
848
849 for &b in output {
850 match b {
851 _ if escaped => {
852 escaped = false;
853 word.push(b);
854 }
855 b'\\' => escaped = true,
856 b'\t' | b'\n' | b'\r' | b' ' => {
857 if !word.is_empty() {
858 words.push(String::from_utf8(word).unwrap());
859 word = Vec::new();
860 }
861 }
862 _ => word.push(b),
863 }
864 }
865
866 if !word.is_empty() {
867 words.push(String::from_utf8(word).unwrap());
868 }
869
870 words
871 }
872
873 #[cfg(test)]
874 mod tests {
875 use super::*;
876
877 #[test]
878 #[cfg(target_os = "macos")]
system_library_mac_test()879 fn system_library_mac_test() {
880 use std::path::Path;
881
882 let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
883
884 assert!(!is_static_available(
885 "PluginManager",
886 &system_roots,
887 &[PathBuf::from("/Library/Frameworks")]
888 ));
889 assert!(!is_static_available(
890 "python2.7",
891 &system_roots,
892 &[PathBuf::from(
893 "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
894 )]
895 ));
896 assert!(!is_static_available(
897 "ffi_convenience",
898 &system_roots,
899 &[PathBuf::from(
900 "/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
901 )]
902 ));
903
904 // Homebrew is in /usr/local, and it's not a part of the OS
905 if Path::new("/usr/local/lib/libpng16.a").exists() {
906 assert!(is_static_available(
907 "png16",
908 &system_roots,
909 &[PathBuf::from("/usr/local/lib")]
910 ));
911
912 let libpng = Config::new()
913 .range_version("1".."99")
914 .probe("libpng16")
915 .unwrap();
916 assert!(libpng.version.find('\n').is_none());
917 }
918 }
919
920 #[test]
921 #[cfg(target_os = "linux")]
system_library_linux_test()922 fn system_library_linux_test() {
923 assert!(!is_static_available(
924 "util",
925 &[PathBuf::from("/usr")],
926 &[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
927 ));
928 assert!(!is_static_available(
929 "dialog",
930 &[PathBuf::from("/usr")],
931 &[PathBuf::from("/usr/lib")]
932 ));
933 }
934
test_library_filename(target: &str, filename: &str)935 fn test_library_filename(target: &str, filename: &str) {
936 assert_eq!(
937 Library::extract_lib_from_filename(target, filename),
938 Some("foo")
939 );
940 }
941
942 #[test]
link_filename_linux()943 fn link_filename_linux() {
944 let target = "x86_64-unknown-linux-gnu";
945 test_library_filename(target, "libfoo.a");
946 test_library_filename(target, "libfoo.so");
947 }
948
949 #[test]
link_filename_apple()950 fn link_filename_apple() {
951 let target = "x86_64-apple-darwin";
952 test_library_filename(target, "libfoo.a");
953 test_library_filename(target, "libfoo.so");
954 test_library_filename(target, "libfoo.dylib");
955 }
956
957 #[test]
link_filename_msvc()958 fn link_filename_msvc() {
959 let target = "x86_64-pc-windows-msvc";
960 // static and dynamic libraries have the same .lib suffix
961 test_library_filename(target, "foo.lib");
962 }
963
964 #[test]
link_filename_mingw()965 fn link_filename_mingw() {
966 let target = "x86_64-pc-windows-gnu";
967 test_library_filename(target, "foo.lib");
968 test_library_filename(target, "libfoo.a");
969 test_library_filename(target, "foo.dll");
970 test_library_filename(target, "foo.dll.a");
971 }
972 }
973