libgpg-error-sys 0.2.2

Raw bindings for libgpg-error
Documentation
extern crate gcc;

use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Child, Stdio};
use std::str;

fn main() {
    let path = env::var_os("GPG_ERROR_LIB_PATH");
    let libs = env::var_os("GPG_ERROR_LIBS");
    if path.is_some() || libs.is_some() {
        let mode = match env::var_os("GPG_ERROR_STATIC") {
            Some(_) => "static",
            _ => "dylib",
        };

        for path in path.iter().flat_map(env::split_paths) {
            println!("cargo:rustc-link-search=native={}", path.display());
        }
        match libs {
            Some(libs) => {
                for lib in env::split_paths(&libs) {
                    println!("cargo:rustc-link-lib={0}={1}", mode, lib.display());
                }
            }
            None => {
                println!("cargo:rustc-link-lib={0}={1}", mode, "gpg-error");
            }
        }
        return;
    } else if let Some(path) = env::var_os("GPG_ERROR_CONFIG") {
        if !try_config(path) {
            process::exit(1);
        }
        return;
    }

    if !Path::new("libgpg-error/.git").exists() {
        run(Command::new("git").args(&["submodule", "update", "--init"]));
    }

    if try_build() || try_config("gpg-error-config") {
        return;
    }
    process::exit(1);
}

fn try_config<S: AsRef<OsStr>>(path: S) -> bool {
    let path = path.as_ref();

    let mut cmd = path.to_owned();
    cmd.push(" --prefix");
    if let Some(output) = output(Command::new("sh").arg("-c").arg(cmd)) {
        println!("cargo:root={}", output);
    }

    let mut cmd = path.to_owned();
    cmd.push(" --mt --libs");
    if let Some(output) = output(Command::new("sh").arg("-c").arg(cmd)) {
        parse_config_output(&output);
        return true;
    }

    let mut cmd = path.to_owned();
    cmd.push(" --libs");
    if let Some(output) = output(Command::new("sh").arg("-c").arg(cmd)) {
        parse_config_output(&output);
        return true;
    }
    false
}

fn parse_config_output(output: &str) {
    let parts = output.split(|c: char| c.is_whitespace()).filter_map(|p| if p.len() > 2 {
        Some(p.split_at(2))
    } else {
        None
    });

    for (flag, val) in parts {
        match flag {
            "-L" => {
                println!("cargo:rustc-link-search=native={}", val);
            }
            "-F" => {
                println!("cargo:rustc-link-search=framework={}", val);
            }
            "-l" => {
                println!("cargo:rustc-link-lib={}", val);
            }
            _ => (),
        }
    }
}

fn try_build() -> bool {
    let src = PathBuf::from(env::current_dir().unwrap()).join("libgpg-error");
    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let build = dst.clone().join("build");
    let target = env::var("TARGET").unwrap();
    let host = env::var("HOST").unwrap();
    let compiler = gcc::Config::new().get_compiler();
    let cflags = compiler.args().iter().fold(OsString::new(), |mut c, a| {
        c.push(a);
        c.push(" ");
        c
    });

    let _ = fs::create_dir_all(&build);

    if !run(Command::new("sh").current_dir(&src).arg("autogen.sh")) {
        return false;
    }
    if !run(Command::new("sh")
        .current_dir(&build)
        .env("CC", compiler.path())
        .env("CFLAGS", cflags)
        .arg(msys_compatible(src.join("configure")))
        .args(&["--build", &gnu_target(&host),
                "--host", &gnu_target(&target),
                "--enable-static",
                "--disable-shared",
                "--disable-doc",
                "--prefix", &msys_compatible(&dst)])) {
        return false;
    }
    if !run(Command::new("make")
        .current_dir(&build)
        .arg("-j")
        .arg(env::var("NUM_JOBS").unwrap())) {
        return false;
    }
    if !run(Command::new("make")
        .current_dir(&build)
        .arg("install")) {
        return false;
    }

    println!("cargo:rustc-link-search=native={}", dst.join("lib").display());
    println!("cargo:rustc-link-lib=static=gpg-error");
    println!("cargo:root={}", dst.display());
    true
}

fn spawn(cmd: &mut Command) -> Option<Child> {
    println!("running: {:?}", cmd);
    match cmd.stdin(Stdio::null()).spawn() {
        Ok(child) => Some(child),
        Err(e) => {
            println!("failed to execute command: {:?}\nerror: {}", cmd, e);
            None
        }
    }
}

fn run(cmd: &mut Command) -> bool {
    if let Some(mut child) = spawn(cmd) {
        match child.wait() {
            Ok(status) => {
                if !status.success() {
                    println!("command did not execute successfully: {:?}\n\
                       expected success, got: {}", cmd, status);
                } else {
                    return true;
                }
            }
            Err(e) => {
                println!("failed to execute command: {:?}\nerror: {}", cmd, e);
            }
        }
    }
    false
}

fn output(cmd: &mut Command) -> Option<String> {
    if let Some(child) = spawn(cmd.stdout(Stdio::piped())) {
        match child.wait_with_output() {
            Ok(output) => {
                if !output.status.success() {
                    println!("command did not execute successfully: {:?}\n\
                       expected success, got: {}", cmd, output.status);
                } else {
                    return String::from_utf8(output.stdout).ok();
                }
            }
            Err(e) => {
                println!("failed to execute command: {:?}\nerror: {}", cmd, e);
            }
        }
    }
    None
}

fn msys_compatible<P: AsRef<Path>>(path: P) -> String {
    use std::ascii::AsciiExt;

    let mut path = path.as_ref().to_string_lossy().into_owned();
    if !cfg!(windows) || Path::new(&path).is_relative() {
        return path;
    }

    if let Some(b'a'...b'z') = path.as_bytes().first().map(u8::to_ascii_lowercase) {
        if path.split_at(1).1.starts_with(":\\") {
            (&mut path[..1]).make_ascii_lowercase();
            path.remove(1);
            path.insert(0, '/');
        }
    }
    path.replace("\\", "/")
}

fn gnu_target(target: &str) -> String {
    match target {
        "i686-pc-windows-gnu" => "i686-w64-mingw32".to_string(),
        "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
        s => s.to_string(),
    }
}