1#![doc(html_root_url = "https://docs.rs/pkg-config/0.3")]
70
71use std::collections::HashMap;
72use std::env;
73use std::error;
74use std::ffi::{OsStr, OsString};
75use std::fmt;
76use std::io;
77use std::ops::{Bound, RangeBounds};
78use std::path::PathBuf;
79use std::process::{Command, Output};
80use std::str;
81
82#[derive(Clone, Debug)]
83pub struct Config {
84 statik: Option<bool>,
85 min_version: Bound<String>,
86 max_version: Bound<String>,
87 extra_args: Vec<OsString>,
88 cargo_metadata: bool,
89 env_metadata: bool,
90 print_system_libs: bool,
91 print_system_cflags: bool,
92}
93
94#[derive(Clone, Debug)]
95pub struct Library {
96 pub libs: Vec<String>,
97 pub link_paths: Vec<PathBuf>,
98 pub frameworks: Vec<String>,
99 pub framework_paths: Vec<PathBuf>,
100 pub include_paths: Vec<PathBuf>,
101 pub ld_args: Vec<Vec<String>>,
102 pub defines: HashMap<String, Option<String>>,
103 pub version: String,
104 _priv: (),
105}
106
107pub enum Error {
109 EnvNoPkgConfig(String),
113
114 CrossCompilation,
120
121 Command { command: String, cause: io::Error },
125
126 Failure { command: String, output: Output },
130
131 ProbeFailure {
135 name: String,
136 command: String,
137 output: Output,
138 },
139
140 #[doc(hidden)]
141 __Nonexhaustive,
143}
144
145impl error::Error for Error {}
146
147impl fmt::Debug for Error {
148 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
149 <Error as fmt::Display>::fmt(self, f)
151 }
152}
153
154impl fmt::Display for Error {
155 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
156 match *self {
157 Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name),
158 Error::CrossCompilation => f.write_str(
159 "pkg-config has not been configured to support cross-compilation.\n\
160 \n\
161 Install a sysroot for the target platform and configure it via\n\
162 PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a\n\
163 cross-compiling wrapper for pkg-config and set it via\n\
164 PKG_CONFIG environment variable.",
165 ),
166 Error::Command {
167 ref command,
168 ref cause,
169 } => {
170 match cause.kind() {
171 io::ErrorKind::NotFound => {
172 let crate_name =
173 std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "sys".to_owned());
174 let instructions = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
175 "Try `brew install pkg-config` if you have Homebrew.\n"
176 } else if cfg!(unix) {
177 "Try `apt install pkg-config`, or `yum install pkg-config`,\n\
178 or `pkg install pkg-config` depending on your distribution.\n"
179 } else {
180 "" };
182 write!(f, "Could not run `{command}`\n\
183 The pkg-config command could not be found.\n\
184 \n\
185 Most likely, you need to install a pkg-config package for your OS.\n\
186 {instructions}\
187 \n\
188 If you've already installed it, ensure the pkg-config command is one of the\n\
189 directories in the PATH environment variable.\n\
190 \n\
191 If you did not expect this build to link to a pre-installed system library,\n\
192 then check documentation of the {crate_name} crate for an option to\n\
193 build the library from source, or disable features or dependencies\n\
194 that require pkg-config.", command = command, instructions = instructions, crate_name = crate_name)
195 }
196 _ => write!(f, "Failed to run command `{}`, because: {}", command, cause),
197 }
198 }
199 Error::ProbeFailure {
200 ref name,
201 ref command,
202 ref output,
203 } => {
204 write!(
205 f,
206 "`{}` did not exit successfully: {}\nerror: could not find system library '{}' required by the '{}' crate\n",
207 command, output.status, name, env::var("CARGO_PKG_NAME").unwrap_or_default(),
208 )?;
209 format_output(output, f)
210 }
211 Error::Failure {
212 ref command,
213 ref output,
214 } => {
215 write!(
216 f,
217 "`{}` did not exit successfully: {}",
218 command, output.status
219 )?;
220 format_output(output, f)
221 }
222 Error::__Nonexhaustive => panic!(),
223 }
224 }
225}
226
227fn format_output(output: &Output, f: &mut fmt::Formatter) -> fmt::Result {
228 let stdout = String::from_utf8_lossy(&output.stdout);
229 if !stdout.is_empty() {
230 write!(f, "\n--- stdout\n{}", stdout)?;
231 }
232 let stderr = String::from_utf8_lossy(&output.stderr);
233 if !stderr.is_empty() {
234 write!(f, "\n--- stderr\n{}", stderr)?;
235 }
236 Ok(())
237}
238
239#[doc(hidden)]
241pub fn find_library(name: &str) -> Result<Library, String> {
242 probe_library(name).map_err(|e| e.to_string())
243}
244
245pub fn probe_library(name: &str) -> Result<Library, Error> {
247 Config::new().probe(name)
248}
249
250#[doc(hidden)]
251#[deprecated(note = "use config.target_supported() instance method instead")]
252pub fn target_supported() -> bool {
253 Config::new().target_supported()
254}
255
256pub fn get_variable(package: &str, variable: &str) -> Result<String, Error> {
264 let arg = format!("--variable={}", variable);
265 let cfg = Config::new();
266 let out = run(cfg.command(package, &[&arg]))?;
267 Ok(str::from_utf8(&out).unwrap().trim_end().to_owned())
268}
269
270impl Config {
271 pub fn new() -> Config {
274 Config {
275 statik: None,
276 min_version: Bound::Unbounded,
277 max_version: Bound::Unbounded,
278 extra_args: vec![],
279 print_system_cflags: true,
280 print_system_libs: true,
281 cargo_metadata: true,
282 env_metadata: true,
283 }
284 }
285
286 pub fn statik(&mut self, statik: bool) -> &mut Config {
291 self.statik = Some(statik);
292 self
293 }
294
295 pub fn atleast_version(&mut self, vers: &str) -> &mut Config {
297 self.min_version = Bound::Included(vers.to_string());
298 self.max_version = Bound::Unbounded;
299 self
300 }
301
302 pub fn exactly_version(&mut self, vers: &str) -> &mut Config {
304 self.min_version = Bound::Included(vers.to_string());
305 self.max_version = Bound::Included(vers.to_string());
306 self
307 }
308
309 pub fn range_version<'a, R>(&mut self, range: R) -> &mut Config
311 where
312 R: RangeBounds<&'a str>,
313 {
314 self.min_version = match range.start_bound() {
315 Bound::Included(vers) => Bound::Included(vers.to_string()),
316 Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
317 Bound::Unbounded => Bound::Unbounded,
318 };
319 self.max_version = match range.end_bound() {
320 Bound::Included(vers) => Bound::Included(vers.to_string()),
321 Bound::Excluded(vers) => Bound::Excluded(vers.to_string()),
322 Bound::Unbounded => Bound::Unbounded,
323 };
324 self
325 }
326
327 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Config {
331 self.extra_args.push(arg.as_ref().to_os_string());
332 self
333 }
334
335 pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
338 self.cargo_metadata = cargo_metadata;
339 self
340 }
341
342 pub fn env_metadata(&mut self, env_metadata: bool) -> &mut Config {
346 self.env_metadata = env_metadata;
347 self
348 }
349
350 pub fn print_system_libs(&mut self, print: bool) -> &mut Config {
355 self.print_system_libs = print;
356 self
357 }
358
359 pub fn print_system_cflags(&mut self, print: bool) -> &mut Config {
364 self.print_system_cflags = print;
365 self
366 }
367
368 #[doc(hidden)]
370 pub fn find(&self, name: &str) -> Result<Library, String> {
371 self.probe(name).map_err(|e| e.to_string())
372 }
373
374 pub fn probe(&self, name: &str) -> Result<Library, Error> {
379 let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name));
380 if self.env_var_os(&abort_var_name).is_some() {
381 return Err(Error::EnvNoPkgConfig(abort_var_name));
382 } else if !self.target_supported() {
383 return Err(Error::CrossCompilation);
384 }
385
386 let mut library = Library::new();
387
388 let output = run(self.command(name, &["--libs", "--cflags"])).map_err(|e| match e {
389 Error::Failure { command, output } => Error::ProbeFailure {
390 name: name.to_owned(),
391 command,
392 output,
393 },
394 other => other,
395 })?;
396 library.parse_libs_cflags(name, &output, self);
397
398 let output = run(self.command(name, &["--modversion"]))?;
399 library.parse_modversion(str::from_utf8(&output).unwrap());
400
401 Ok(library)
402 }
403
404 pub fn target_supported(&self) -> bool {
406 let target = env::var_os("TARGET").unwrap_or_default();
407 let host = env::var_os("HOST").unwrap_or_default();
408
409 if host == target {
412 return true;
413 }
414
415 match self.targetted_env_var("PKG_CONFIG_ALLOW_CROSS") {
418 Some(ref val) if val == "0" => false,
420 Some(_) => true,
421 None => {
422 self.targetted_env_var("PKG_CONFIG").is_some()
425 || self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
426 }
427 }
428 }
429
430 #[doc(hidden)]
432 pub fn get_variable(package: &str, variable: &str) -> Result<String, String> {
433 get_variable(package, variable).map_err(|e| e.to_string())
434 }
435
436 fn targetted_env_var(&self, var_base: &str) -> Option<OsString> {
437 match (env::var("TARGET"), env::var("HOST")) {
438 (Ok(target), Ok(host)) => {
439 let kind = if host == target { "HOST" } else { "TARGET" };
440 let target_u = target.replace("-", "_");
441
442 self.env_var_os(&format!("{}_{}", var_base, target))
443 .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u)))
444 .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base)))
445 .or_else(|| self.env_var_os(var_base))
446 }
447 (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => {
448 self.env_var_os(var_base)
449 }
450 (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => {
451 panic!(
452 "HOST or TARGET environment variable is not valid unicode: {:?}",
453 s
454 )
455 }
456 }
457 }
458
459 fn env_var_os(&self, name: &str) -> Option<OsString> {
460 if self.env_metadata {
461 println!("cargo:rerun-if-env-changed={}", name);
462 }
463 env::var_os(name)
464 }
465
466 fn is_static(&self, name: &str) -> bool {
467 self.statik.unwrap_or_else(|| self.infer_static(name))
468 }
469
470 fn command(&self, name: &str, args: &[&str]) -> Command {
471 let exe = self
472 .targetted_env_var("PKG_CONFIG")
473 .unwrap_or_else(|| OsString::from("pkg-config"));
474 let mut cmd = Command::new(exe);
475 if self.is_static(name) {
476 cmd.arg("--static");
477 }
478 cmd.args(args).args(&self.extra_args);
479
480 if let Some(value) = self.targetted_env_var("PKG_CONFIG_PATH") {
481 cmd.env("PKG_CONFIG_PATH", value);
482 }
483 if let Some(value) = self.targetted_env_var("PKG_CONFIG_LIBDIR") {
484 cmd.env("PKG_CONFIG_LIBDIR", value);
485 }
486 if let Some(value) = self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR") {
487 cmd.env("PKG_CONFIG_SYSROOT_DIR", value);
488 }
489 if self.print_system_libs {
490 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
491 }
492 if self.print_system_cflags {
493 cmd.env("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1");
494 }
495 cmd.arg(name);
496 match self.min_version {
497 Bound::Included(ref version) => {
498 cmd.arg(&format!("{} >= {}", name, version));
499 }
500 Bound::Excluded(ref version) => {
501 cmd.arg(&format!("{} > {}", name, version));
502 }
503 _ => (),
504 }
505 match self.max_version {
506 Bound::Included(ref version) => {
507 cmd.arg(&format!("{} <= {}", name, version));
508 }
509 Bound::Excluded(ref version) => {
510 cmd.arg(&format!("{} < {}", name, version));
511 }
512 _ => (),
513 }
514 cmd
515 }
516
517 fn print_metadata(&self, s: &str) {
518 if self.cargo_metadata {
519 println!("cargo:{}", s);
520 }
521 }
522
523 fn infer_static(&self, name: &str) -> bool {
524 let name = envify(name);
525 if self.env_var_os(&format!("{}_STATIC", name)).is_some() {
526 true
527 } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() {
528 false
529 } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() {
530 true
531 } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
532 false
533 } else {
534 false
535 }
536 }
537}
538
539impl Default for Config {
541 fn default() -> Config {
542 Config {
543 statik: None,
544 min_version: Bound::Unbounded,
545 max_version: Bound::Unbounded,
546 extra_args: vec![],
547 print_system_cflags: false,
548 print_system_libs: false,
549 cargo_metadata: false,
550 env_metadata: false,
551 }
552 }
553}
554
555impl Library {
556 fn new() -> Library {
557 Library {
558 libs: Vec::new(),
559 link_paths: Vec::new(),
560 include_paths: Vec::new(),
561 ld_args: Vec::new(),
562 frameworks: Vec::new(),
563 framework_paths: Vec::new(),
564 defines: HashMap::new(),
565 version: String::new(),
566 _priv: (),
567 }
568 }
569
570 fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
571 let mut is_msvc = false;
572 if let Ok(target) = env::var("TARGET") {
573 if target.contains("msvc") {
574 is_msvc = true;
575 }
576 }
577
578 let mut dirs = Vec::new();
579 let statik = config.is_static(name);
580
581 let words = split_flags(output);
582
583 let parts = words
585 .iter()
586 .filter(|l| l.len() > 2)
587 .map(|arg| (&arg[0..2], &arg[2..]));
588 for (flag, val) in parts {
589 match flag {
590 "-L" => {
591 let meta = format!("rustc-link-search=native={}", val);
592 config.print_metadata(&meta);
593 dirs.push(PathBuf::from(val));
594 self.link_paths.push(PathBuf::from(val));
595 }
596 "-F" => {
597 let meta = format!("rustc-link-search=framework={}", val);
598 config.print_metadata(&meta);
599 self.framework_paths.push(PathBuf::from(val));
600 }
601 "-I" => {
602 self.include_paths.push(PathBuf::from(val));
603 }
604 "-l" => {
605 if is_msvc && ["m", "c", "pthread"].contains(&val) {
607 continue;
608 }
609
610 if statik {
611 let meta = format!("rustc-link-lib=static={}", val);
612 config.print_metadata(&meta);
613 } else {
614 let meta = format!("rustc-link-lib={}", val);
615 config.print_metadata(&meta);
616 }
617
618 self.libs.push(val.to_string());
619 }
620 "-D" => {
621 let mut iter = val.split('=');
622 self.defines.insert(
623 iter.next().unwrap().to_owned(),
624 iter.next().map(|s| s.to_owned()),
625 );
626 }
627 _ => {}
628 }
629 }
630
631 let mut iter = words.iter().flat_map(|arg| {
633 if arg.starts_with("-Wl,") {
634 arg[4..].split(',').collect()
635 } else {
636 vec![arg.as_ref()]
637 }
638 });
639 while let Some(part) = iter.next() {
640 match part {
641 "-framework" => {
642 if let Some(lib) = iter.next() {
643 let meta = format!("rustc-link-lib=framework={}", lib);
644 config.print_metadata(&meta);
645 self.frameworks.push(lib.to_string());
646 }
647 }
648 "-isystem" | "-iquote" | "-idirafter" => {
649 if let Some(inc) = iter.next() {
650 self.include_paths.push(PathBuf::from(inc));
651 }
652 }
653 _ => (),
654 }
655 }
656
657 let mut linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,"));
658 while let Some(option) = linker_options.next() {
659 let mut pop = false;
660 let mut ld_option = vec![];
661 for subopt in option[4..].split(',') {
662 if pop {
663 pop = false;
664 continue;
665 }
666
667 if subopt == "-framework" {
668 pop = true;
669 continue;
670 }
671
672 ld_option.push(subopt);
673 }
674
675 let meta = format!("rustc-link-arg=-Wl,{}", ld_option.join(","));
676 config.print_metadata(&meta);
677
678 self.ld_args
679 .push(ld_option.into_iter().map(String::from).collect());
680 }
681 }
682
683 fn parse_modversion(&mut self, output: &str) {
684 self.version.push_str(output.lines().nth(0).unwrap().trim());
685 }
686}
687
688fn envify(name: &str) -> String {
689 name.chars()
690 .map(|c| c.to_ascii_uppercase())
691 .map(|c| if c == '-' { '_' } else { c })
692 .collect()
693}
694
695fn run(mut cmd: Command) -> Result<Vec<u8>, Error> {
706 match cmd.output() {
707 Ok(output) => {
708 if output.status.success() {
709 Ok(output.stdout)
710 } else {
711 Err(Error::Failure {
712 command: format!("{:?}", cmd),
713 output,
714 })
715 }
716 }
717 Err(cause) => Err(Error::Command {
718 command: format!("{:?}", cmd),
719 cause,
720 }),
721 }
722}
723
724fn split_flags(output: &[u8]) -> Vec<String> {
732 let mut word = Vec::new();
733 let mut words = Vec::new();
734 let mut escaped = false;
735
736 for &b in output {
737 match b {
738 _ if escaped => {
739 escaped = false;
740 word.push(b);
741 }
742 b'\\' => escaped = true,
743 b'\t' | b'\n' | b'\r' | b' ' => {
744 if !word.is_empty() {
745 words.push(String::from_utf8(word).unwrap());
746 word = Vec::new();
747 }
748 }
749 _ => word.push(b),
750 }
751 }
752
753 if !word.is_empty() {
754 words.push(String::from_utf8(word).unwrap());
755 }
756
757 words
758}
759
760#[test]
761#[cfg(target_os = "macos")]
762fn system_library_mac_test() {
763 use std::path::Path;
764
765 let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
766
767 assert!(!is_static_available(
768 "PluginManager",
769 &system_roots,
770 &[PathBuf::from("/Library/Frameworks")]
771 ));
772 assert!(!is_static_available(
773 "python2.7",
774 &system_roots,
775 &[PathBuf::from(
776 "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
777 )]
778 ));
779 assert!(!is_static_available(
780 "ffi_convenience",
781 &system_roots,
782 &[PathBuf::from(
783 "/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
784 )]
785 ));
786
787 if Path::new("/usr/local/lib/libpng16.a").exists() {
789 assert!(is_static_available(
790 "png16",
791 &system_roots,
792 &[PathBuf::from("/usr/local/lib")]
793 ));
794
795 let libpng = Config::new()
796 .range_version("1".."99")
797 .probe("libpng16")
798 .unwrap();
799 assert!(libpng.version.find('\n').is_none());
800 }
801}
802
803