1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#![allow(unstable)]

use std::io::Command;
use std::io::fs::PathExtensions;
use std::os;
use std::str;

pub fn target_supported() -> bool {
    os::getenv("HOST") == os::getenv("TARGET") ||
        os::getenv("PKG_CONFIG_ALLOW_CROSS") == Some("1".to_string())
}

pub struct Options {
    pub statik: bool,
    pub atleast_version: Option<String>,
}

pub fn find_library(name: &str) -> Result<(), String> {
    find_library_opts(name, &default_options(name))
}

pub fn find_library_opts(name: &str, options: &Options) -> Result<(), String> {
    if os::getenv(format!("{}_NO_PKG_CONFIG", envify(name)).as_slice()).is_some() {
        return Err(format!("pkg-config requested to be aborted for {}", name))
    } else if !target_supported() {
        return Err("pkg-config doesn't handle cross compilation. Use \
                    PKG_CONFIG_ALLOW_CROSS=1 to override".to_string());
    }
    let mut cmd = Command::new("pkg-config");
    if options.statik {
        cmd.arg("--static");
    }
    cmd.arg("--libs")
       .env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1")
       .arg(name);
    match options.atleast_version {
        Some(ref v) => { cmd.arg(format!("--atleast-version={}", v)); }
        None => {}
    }
    let out = try!(cmd.output().map_err(|e| {
        format!("failed to run `{}`: {}", cmd, e)
    }));
    let stdout = str::from_utf8(out.output.as_slice()).unwrap();
    let stderr = str::from_utf8(out.error.as_slice()).unwrap();
    if !out.status.success() {
        let mut msg = format!("`{}` did not exit successfully: {}", cmd,
                              out.status);
        if stdout.len() > 0 {
            msg.push_str("\n--- stdout\n");
            msg.push_str(stdout);
        }
        if stderr.len() > 0 {
            msg.push_str("\n--- stderr\n");
            msg.push_str(stderr);
        }
        return Err(msg)
    }

    let mut dirs = Vec::new();
    let parts = stdout.split(' ').filter(|l| !l.is_empty() && l.len() > 2)
                      .map(|arg| (&arg[0..2], &arg[2..]))
                      .collect::<Vec<_>>();
    for &(flag, val) in parts.iter() {
        if flag == "-L" {
            println!("cargo:rustc-flags=-L native={}", val);
            dirs.push(Path::new(val));
        }
    }
    for &(flag, val) in parts.iter() {
        if flag == "-l" {
            if options.statik && !is_system_lib(val, &dirs[]) {
                println!("cargo:rustc-flags=-l {}:static", val);
            } else {
                println!("cargo:rustc-flags=-l {}", val);
            }
        }
    }
    Ok(())
}

pub fn default_options(name: &str) -> Options {
    let name = envify(name);
    let statik = if os::getenv(format!("{}_STATIC", name).as_slice()).is_some() {
        true
    } else if os::getenv(format!("{}_DYNAMIC", name).as_slice()).is_some() {
        false
    } else if os::getenv("PKG_CONFIG_ALL_STATIC").is_some() {
        true
    } else if os::getenv("PKG_CONFIG_ALL_DYNAMIC").is_some() {
        false
    } else {
        false
    };
    Options { statik: statik, atleast_version: None }
}

fn envify(name: &str) -> String {
    name.chars().map(|c| c.to_uppercase()).map(|c| if c == '-' {'_'} else {c})
        .collect()
}

fn is_system_lib(name: &str, dirs: &[Path]) -> bool {
    let libname = format!("lib{}.a", name);
    let root = Path::new("/usr");
    !dirs.iter().any(|d| {
        !root.is_ancestor_of(d) && d.join(&libname).exists()
    })
}