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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Target-aware environment manipulations.
//!
//! cc-rs and pkg-config-rs use a similar convention where some environment
//! variables (like CC, CFLAGS or PKG_CONFIG_PATH) can be tagged with the
//! current rustc target to distinguish a native build environment and one
//! or several cross-compilation ones.
//!
//! For instance, while compiling for Android arm, `cc-rs` looks first at 
//! CC_arm-linux-androideabi, then CC_arm_linux_androideabi, the TARGET_CC
//! and finally CC.
//!
//! This crates implements some of the same logic and also helps generating
//! these variables names. It also notify all environment lookup "back" to 
//! cargo using `cargo:rerun-if-env-changed` markup.

use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::PathBuf;
use super::Result;
use super::ResultExt;


/// Append a value to a PATH-like (`:`-separated) environment variable.
pub fn append_path_to_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
    debug!("Appending {:?} to {:?}", value.as_ref(), key.as_ref());
    let mut formatted_value = OsString::new();
    if let Ok(initial_value) = env::var(key.as_ref()) {
        formatted_value.push(initial_value);
        formatted_value.push(":");
    }
    formatted_value.push(value);
    env::set_var(key.as_ref(), formatted_value);
}

/// Append a value to a PATH-like (`:`-separated) environment variable taking
/// target scoping rules into consideration.
pub fn append_path_to_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(
    k: K, rustc_triple: Option<R>, v: V) {
    append_path_to_env(target_key_from_triple(k, rustc_triple), v.as_ref())
}

/// Build-context aware environment variable access.
///
/// If we are running in a build.rs context, register the var to cargo using
/// `cargo:rerun-if-env-changed`.
pub fn build_env(name: &str) -> Result<String> {
    let is_build_rs = env::var("CARGO_PKG_NAME").is_ok() && env::var("OUT_DIR").is_ok();

    if is_build_rs {
        println!("cargo:rerun-if-env-changed={}", name);
    }
    Ok(env::var(name)?)
}

/// Capitalize and replace `-` by `_`.
pub fn envify<S: AsRef<str>>(name: S) -> String {
    name.as_ref()
        .chars()
        .map(|c| c.to_ascii_uppercase())
        .map(|c| { if c == '-' || c == '.' { '_' } else { c } })
        .collect()
}

/// Set a bunch of environment variables.
pub fn set_all_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(env: &[(K, V)]) {
    for env_var in env {
        set_env(env_var.0.as_ref(), env_var.1.as_ref())
    }
}

/// Set one environment variable.
pub fn set_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {
    debug!("Setting environment variable {:?}={:?}", k.as_ref(), v.as_ref());
    env::set_var(k, v);
}

/// Set one environment variable if not set yet.
pub fn set_env_ifndef<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {
    if let Ok(current_env_value) = env::var(k.as_ref()) {
        debug!("Ignoring value {:?} as environment variable {:?} already defined with value {:?}",
               k.as_ref(), v.as_ref(), current_env_value);
    } else {
        debug!("Setting environment variable {:?}={:?}", k.as_ref(), v.as_ref());
        env::set_var(k, v);
    }
}

/// Set one environment variable with target-scoping rules.
pub fn set_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(k: K, rustc_triple: Option<R>, v: V) {
    set_env(target_key_from_triple(k, rustc_triple), v);
}

/// Access a required TARGET_SYSROOT variable, suggesting to define it or use
/// Dinghy.
pub fn sysroot_path() -> Result<PathBuf> {
    env::var_os("TARGET_SYSROOT").map(PathBuf::from).chain_err(|| "You must either define a TARGET_SYSROOT or use Dinghy to build your project.")
}

/// Access `var_base` directly, or use targetting rules depending on the build
/// being native or cross.
pub fn target_env(var_base: &str) -> Result<String> {
    if let Ok(target) = env::var("TARGET") {
        let is_host = env::var("HOST")? == target;
        target_env_from_triple(var_base, target.as_str(), is_host)
    } else {
        build_env(var_base)
    }
}

/// Access `var_base` directly, using targetting rules.
pub fn target_env_from_triple(var_base: &str, triple: &str, is_host: bool) -> Result<String> {
    build_env(&format!("{}_{}", var_base, triple))
        .or_else(|_| build_env(&format!("{}_{}", var_base, triple.replace("-", "_"))))
        .or_else(|_| build_env(&format!("{}_{}", if is_host { "HOST" } else { "TARGET" }, var_base)))
        .or_else(|_| build_env(var_base))
}

fn target_key_from_triple<K: AsRef<OsStr>, R: AsRef<str>>(k: K, rustc_triple: Option<R>) -> OsString {
    let mut target_key = OsString::new();
    target_key.push(k);
    if let Some(rustc_triple) = rustc_triple {
        target_key.push("_");
        target_key.push(rustc_triple.as_ref().replace("-", "_"));
    }
    target_key
}