ya_pkg_config/
lib.rs

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//! extern crate ya_pkg_config;
42//!
43//! fn main() {
44//!     ya_pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
45//! }
46//! ```
47//!
48//! Find the system library named `foo`, with no version requirement (not
49//! recommended):
50//!
51//! ```no_run
52//! extern crate ya_pkg_config;
53//!
54//! fn main() {
55//!     ya_pkg_config::probe_library("foo").unwrap();
56//! }
57//! ```
58//!
59//! Configure how library `foo` is linked to.
60//!
61//! ```no_run
62//! extern crate ya_pkg_config;
63//!
64//! fn main() {
65//!     ya_pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
66//! }
67//! ```
68
69#![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
107/// Represents all reasons `pkg-config` might not succeed or be run at all.
108pub enum Error {
109    /// Aborted because of `*_NO_PKG_CONFIG` environment variable.
110    ///
111    /// Contains the name of the responsible environment variable.
112    EnvNoPkgConfig(String),
113
114    /// Detected cross compilation without a custom sysroot.
115    ///
116    /// Ignore the error with `PKG_CONFIG_ALLOW_CROSS=1`,
117    /// which may let `pkg-config` select libraries
118    /// for the host's architecture instead of the target's.
119    CrossCompilation,
120
121    /// Failed to run `pkg-config`.
122    ///
123    /// Contains the command and the cause.
124    Command { command: String, cause: io::Error },
125
126    /// `pkg-config` did not exit sucessfully after probing a library.
127    ///
128    /// Contains the command and output.
129    Failure { command: String, output: Output },
130
131    /// `pkg-config` did not exit sucessfully on the first attempt to probe a library.
132    ///
133    /// Contains the command and output.
134    ProbeFailure {
135        name: String,
136        command: String,
137        output: Output,
138    },
139
140    #[doc(hidden)]
141    // please don't match on this, we're likely to add more variants over time
142    __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        // Failed `unwrap()` prints Debug representation, but the default debug format lacks helpful instructions for the end users
150        <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                            "" // There's no easy fix for Windows users
181                        };
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/// Deprecated in favor of the probe_library function
240#[doc(hidden)]
241pub fn find_library(name: &str) -> Result<Library, String> {
242    probe_library(name).map_err(|e| e.to_string())
243}
244
245/// Simple shortcut for using all default options for finding a library.
246pub 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
256/// Run `pkg-config` to get the value of a variable from a package using
257/// `--variable`.
258///
259/// The content of `PKG_CONFIG_SYSROOT_DIR` is not injected in paths that are
260/// returned by `pkg-config --variable`, which makes them unsuitable to use
261/// during cross-compilation unless specifically designed to be used
262/// at that time.
263pub 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    /// Creates a new set of configuration options which are all initially set
272    /// to "blank".
273    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    /// Indicate whether the `--static` flag should be passed.
287    ///
288    /// This will override the inference from environment variables described in
289    /// the crate documentation.
290    pub fn statik(&mut self, statik: bool) -> &mut Config {
291        self.statik = Some(statik);
292        self
293    }
294
295    /// Indicate that the library must be at least version `vers`.
296    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    /// Indicate that the library must be equal to version `vers`.
303    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    /// Indicate that the library's version must be in `range`.
310    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    /// Add an argument to pass to pkg-config.
328    ///
329    /// It's placed after all of the arguments generated by this library.
330    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    /// Define whether metadata should be emitted for cargo allowing it to
336    /// automatically link the binary. Defaults to `true`.
337    pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
338        self.cargo_metadata = cargo_metadata;
339        self
340    }
341
342    /// Define whether metadata should be emitted for cargo allowing to
343    /// automatically rebuild when environment variables change. Defaults to
344    /// `true`.
345    pub fn env_metadata(&mut self, env_metadata: bool) -> &mut Config {
346        self.env_metadata = env_metadata;
347        self
348    }
349
350    /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_LIBS` environment
351    /// variable.
352    ///
353    /// This env var is enabled by default.
354    pub fn print_system_libs(&mut self, print: bool) -> &mut Config {
355        self.print_system_libs = print;
356        self
357    }
358
359    /// Enable or disable the `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS` environment
360    /// variable.
361    ///
362    /// This env var is enabled by default.
363    pub fn print_system_cflags(&mut self, print: bool) -> &mut Config {
364        self.print_system_cflags = print;
365        self
366    }
367
368    /// Deprecated in favor fo the `probe` function
369    #[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    /// Run `pkg-config` to find the library `name`.
375    ///
376    /// This will use all configuration previously set to specify how
377    /// `pkg-config` is run.
378    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    /// True if pkg-config is used for the host system, or configured for cross-compilation
405    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        // Only use pkg-config in host == target situations by default (allowing an
410        // override).
411        if host == target {
412            return true;
413        }
414
415        // pkg-config may not be aware of cross-compilation, and require
416        // a wrapper script that sets up platform-specific prefixes.
417        match self.targetted_env_var("PKG_CONFIG_ALLOW_CROSS") {
418            // don't use pkg-config if explicitly disabled
419            Some(ref val) if val == "0" => false,
420            Some(_) => true,
421            None => {
422                // if not disabled, and pkg-config is customized,
423                // then assume it's prepared for cross-compilation
424                self.targetted_env_var("PKG_CONFIG").is_some()
425                    || self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
426            }
427        }
428    }
429
430    /// Deprecated in favor of the top level `get_variable` function
431    #[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
539// Implement Default manualy since Bound does not implement Default.
540impl 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        // Handle single-character arguments like `-I/usr/include`
584        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                    // These are provided by the CRT with MSVC
606                    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        // Handle multi-character arguments with space-separated value like `-framework foo`
632        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
695/// System libraries should only be linked dynamically
696/// Really?
697// fn is_static_available(name: &str, system_roots: &[PathBuf], dirs: &[PathBuf]) -> bool {
698//     let libname = format!("lib{}.a", name);
699
700//     dirs.iter().any(|dir| {
701//         !system_roots.iter().any(|sys| dir.starts_with(sys)) && dir.join(&libname).exists()
702//     })
703// }
704
705fn 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
724/// Split output produced by pkg-config --cflags and / or --libs into separate flags.
725///
726/// Backslash in output is used to preserve literal meaning of following byte.  Different words are
727/// separated by unescaped space. Other whitespace characters generally should not occur unescaped
728/// at all, apart from the newline at the end of output. For compatibility with what others
729/// consumers of pkg-config output would do in this scenario, they are used here for splitting as
730/// well.
731fn 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    // Homebrew is in /usr/local, and it's not a part of the OS
788    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// #[test]
804// #[cfg(target_os = "linux")]
805// fn system_library_linux_test() {
806//     assert!(!is_static_available(
807//         "util",
808//         &[PathBuf::from("/usr")],
809//         &[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
810//     ));
811//     assert!(!is_static_available(
812//         "dialog",
813//         &[PathBuf::from("/usr")],
814//         &[PathBuf::from("/usr/lib")]
815//     ));
816// }