dinghy_build/
build_env.rs

1//! Target-aware environment manipulations.
2//!
3//! cc-rs and pkg-config-rs use a similar convention where some environment
4//! variables (like CC, CFLAGS or PKG_CONFIG_PATH) can be tagged with the
5//! current rustc target to distinguish a native build environment and one
6//! or several cross-compilation ones.
7//!
8//! For instance, while compiling for Android arm, `cc-rs` looks first at
9//! CC_arm-linux-androideabi, then CC_arm_linux_androideabi, the TARGET_CC
10//! and finally CC.
11//!
12//! This crates implements some of the same logic and also helps generating
13//! these variables names. It also notify all environment lookup "back" to
14//! cargo using `cargo:rerun-if-env-changed` markup.
15
16use anyhow::{Context, Result};
17use std::env;
18use std::ffi::OsStr;
19use std::ffi::OsString;
20use std::path::PathBuf;
21
22/// Append a value to a PATH-like (`:`-separated) environment variable.
23pub fn append_path_to_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
24    let mut formatted_value = OsString::new();
25    if let Ok(initial_value) = env::var(key.as_ref()) {
26        formatted_value.push(initial_value);
27        formatted_value.push(":");
28    }
29    formatted_value.push(value);
30    env::set_var(key.as_ref(), formatted_value);
31}
32
33/// Append a value to a PATH-like (`:`-separated) environment variable taking
34/// target scoping rules into consideration.
35pub fn append_path_to_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(
36    k: K,
37    rustc_triple: Option<R>,
38    v: V,
39) {
40    append_path_to_env(target_key_from_triple(k, rustc_triple), v.as_ref())
41}
42
43/// Build-context aware environment variable access.
44///
45/// If we are running in a build.rs context, register the var to cargo using
46/// `cargo:rerun-if-env-changed`.
47pub fn build_env(name: &str) -> Result<String> {
48    let is_build_rs = env::var("CARGO_PKG_NAME").is_ok() && env::var("OUT_DIR").is_ok();
49
50    if is_build_rs {
51        println!("cargo:rerun-if-env-changed={}", name);
52    }
53    Ok(env::var(name)?)
54}
55
56/// Capitalize and replace `-` by `_`.
57pub fn envify<S: AsRef<str>>(name: S) -> String {
58    name.as_ref()
59        .chars()
60        .map(|c| c.to_ascii_uppercase())
61        .map(|c| if c == '-' || c == '.' { '_' } else { c })
62        .collect()
63}
64
65/// Set a bunch of environment variables.
66pub fn set_all_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(env: &[(K, V)]) {
67    for env_var in env {
68        set_env(env_var.0.as_ref(), env_var.1.as_ref())
69    }
70}
71
72/// Set one environment variable.
73pub fn set_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {
74    log::debug!(
75        "Setting environment variable {:?}={:?}",
76        k.as_ref(),
77        v.as_ref()
78    );
79    env::set_var(k, v);
80}
81
82/// Set one environment variable if not set yet.
83pub fn set_env_ifndef<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {
84    if let Ok(current_env_value) = env::var(k.as_ref()) {
85        log::debug!(
86            "Ignoring value {:?} as environment variable {:?} already defined with value {:?}",
87            k.as_ref(),
88            v.as_ref(),
89            current_env_value
90        );
91    } else {
92        log::debug!(
93            "Setting environment variable {:?}={:?}",
94            k.as_ref(),
95            v.as_ref()
96        );
97        env::set_var(k, v);
98    }
99}
100
101/// Set one environment variable with target-scoping rules.
102pub fn set_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(
103    k: K,
104    rustc_triple: Option<R>,
105    v: V,
106) {
107    set_env(target_key_from_triple(k, rustc_triple), v);
108}
109
110/// Access a required TARGET_SYSROOT variable, suggesting to define it or use
111/// Dinghy.
112pub fn sysroot_path() -> Result<PathBuf> {
113    env::var_os("TARGET_SYSROOT")
114        .map(PathBuf::from)
115        .context("You must either define a TARGET_SYSROOT or use Dinghy to build your project.")
116}
117
118/// Access `var_base` directly, or use targetting rules depending on the build
119/// being native or cross.
120pub fn target_env(var_base: &str) -> Result<String> {
121    if let Ok(target) = env::var("TARGET") {
122        let is_host = env::var("HOST")? == target;
123        target_env_from_triple(var_base, target.as_str(), is_host)
124    } else {
125        build_env(var_base)
126    }
127}
128
129/// Access `var_base` directly, using targetting rules.
130pub fn target_env_from_triple(var_base: &str, triple: &str, is_host: bool) -> Result<String> {
131    build_env(&format!("{}_{}", var_base, triple))
132        .or_else(|_| build_env(&format!("{}_{}", var_base, triple.replace("-", "_"))))
133        .or_else(|_| {
134            build_env(&format!(
135                "{}_{}",
136                if is_host { "HOST" } else { "TARGET" },
137                var_base
138            ))
139        })
140        .or_else(|_| build_env(var_base))
141}
142
143fn target_key_from_triple<K: AsRef<OsStr>, R: AsRef<str>>(
144    k: K,
145    rustc_triple: Option<R>,
146) -> OsString {
147    let mut target_key = OsString::new();
148    target_key.push(k);
149    if let Some(rustc_triple) = rustc_triple {
150        target_key.push("_");
151        target_key.push(rustc_triple.as_ref().replace("-", "_"));
152    }
153    target_key
154}