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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! 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 anyhow::{Context, Result};
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::PathBuf;

/// 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) {
    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) {
    log::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()) {
        log::debug!(
            "Ignoring value {:?} as environment variable {:?} already defined with value {:?}",
            k.as_ref(),
            v.as_ref(),
            current_env_value
        );
    } else {
        log::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)
        .context("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
}