Allow env vars set in cargo.extraEnv to be resolved by the env! macro
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 04fb227..76ba01f 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -49,8 +49,9 @@
is_virtual_workspace: bool,
/// Whether this workspace represents the sysroot workspace.
is_sysroot: bool,
- /// Environment variables set in the `.cargo/config` file.
- config_env: Env,
+ /// Environment variables set in the `.cargo/config` file and the extraEnv
+ /// configuration option.
+ env: Env,
requires_rustc_private: bool,
}
@@ -325,7 +326,7 @@
pub fn new(
mut meta: cargo_metadata::Metadata,
ws_manifest_path: ManifestPath,
- cargo_config_env: Env,
+ cargo_env: Env,
is_sysroot: bool,
) -> CargoWorkspace {
let mut pkg_by_id = FxHashMap::default();
@@ -498,7 +499,7 @@
is_virtual_workspace,
requires_rustc_private,
is_sysroot,
- config_env: cargo_config_env,
+ env: cargo_env,
}
}
@@ -589,7 +590,7 @@
}
pub fn env(&self) -> &Env {
- &self.config_env
+ &self.env
}
pub fn is_sysroot(&self) -> bool {
diff --git a/crates/project-model/src/env.rs b/crates/project-model/src/env.rs
index d281492..ae0458a 100644
--- a/crates/project-model/src/env.rs
+++ b/crates/project-model/src/env.rs
@@ -1,6 +1,7 @@
//! Cargo-like environment variables injection.
use base_db::Env;
use paths::Utf8Path;
+use rustc_hash::FxHashMap;
use toolchain::Tool;
use crate::{ManifestPath, PackageData, TargetKind, cargo_config_file::CargoConfigFile};
@@ -60,8 +61,14 @@
env.set("CARGO_CRATE_NAME", cargo_name.replace('-', "_"));
}
-pub(crate) fn cargo_config_env(manifest: &ManifestPath, config: &Option<CargoConfigFile>) -> Env {
+pub(crate) fn cargo_config_env(
+ manifest: &ManifestPath,
+ config: &Option<CargoConfigFile>,
+ extra_env: &FxHashMap<String, Option<String>>,
+) -> Env {
let mut env = Env::default();
+ env.extend(extra_env.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k.clone(), v.clone()))));
+
let Some(serde_json::Value::Object(env_json)) = config.as_ref().and_then(|c| c.get("env"))
else {
return env;
@@ -72,22 +79,34 @@
let base = <_ as AsRef<Utf8Path>>::as_ref(manifest.parent());
for (key, entry) in env_json {
- let serde_json::Value::Object(entry) = entry else {
- continue;
- };
- let Some(value) = entry.get("value").and_then(|v| v.as_str()) else {
- continue;
+ let value = match entry {
+ serde_json::Value::String(s) => s.clone(),
+ serde_json::Value::Object(entry) => {
+ // Each entry MUST have a `value` key.
+ let Some(value) = entry.get("value").and_then(|v| v.as_str()) else {
+ continue;
+ };
+ // If the entry already exists in the environment AND the `force` key is not set to
+ // true, then don't overwrite the value.
+ if extra_env.get(key).is_some_and(Option::is_some)
+ && !entry.get("force").and_then(|v| v.as_bool()).unwrap_or(false)
+ {
+ continue;
+ }
+
+ if entry
+ .get("relative")
+ .and_then(|v| v.as_bool())
+ .is_some_and(std::convert::identity)
+ {
+ base.join(value).to_string()
+ } else {
+ value.to_owned()
+ }
+ }
+ _ => continue,
};
- let value = if entry
- .get("relative")
- .and_then(|v| v.as_bool())
- .is_some_and(std::convert::identity)
- {
- base.join(value).to_string()
- } else {
- value.to_owned()
- };
env.insert(key, value);
}
@@ -113,7 +132,19 @@
},
"TEST": {
"value": "test"
- }
+ },
+ "FORCED": {
+ "value": "test",
+ "force": true
+ },
+ "UNFORCED": {
+ "value": "test",
+ "force": false
+ },
+ "OVERWRITTEN": {
+ "value": "test"
+ },
+ "NOT_AN_OBJECT": "value"
}
}
"#;
@@ -121,9 +152,22 @@
let cwd = paths::Utf8PathBuf::try_from(std::env::current_dir().unwrap()).unwrap();
let manifest = paths::AbsPathBuf::assert(cwd.join("Cargo.toml"));
let manifest = ManifestPath::try_from(manifest).unwrap();
- let env = cargo_config_env(&manifest, &Some(config));
+ let extra_env = [
+ ("FORCED", Some("ignored")),
+ ("UNFORCED", Some("newvalue")),
+ ("OVERWRITTEN", Some("newvalue")),
+ ("TEST", None),
+ ]
+ .iter()
+ .map(|(k, v)| (k.to_string(), v.map(ToString::to_string)))
+ .collect();
+ let env = cargo_config_env(&manifest, &Some(config), &extra_env);
assert_eq!(env.get("CARGO_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str()));
assert_eq!(env.get("RELATIVE").as_deref(), Some(cwd.join("../relative").as_str()));
assert_eq!(env.get("INVALID").as_deref(), Some("../relative"));
assert_eq!(env.get("TEST").as_deref(), Some("test"));
+ assert_eq!(env.get("FORCED").as_deref(), Some("test"));
+ assert_eq!(env.get("UNFORCED").as_deref(), Some("newvalue"));
+ assert_eq!(env.get("OVERWRITTEN").as_deref(), Some("newvalue"));
+ assert_eq!(env.get("NOT_AN_OBJECT").as_deref(), Some("value"));
}
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 22b8479..b88db41 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -387,36 +387,36 @@
progress,
)
});
- let cargo_config_extra_env =
- s.spawn(move || cargo_config_env(cargo_toml, &config_file));
+ let cargo_env =
+ s.spawn(move || cargo_config_env(cargo_toml, &config_file, &config.extra_env));
thread::Result::Ok((
rustc_cfg.join()?,
target_data.join()?,
rustc_dir.join()?,
loaded_sysroot.join()?,
cargo_metadata.join()?,
- cargo_config_extra_env.join()?,
+ cargo_env.join()?,
))
});
- let (
- rustc_cfg,
- data_layout,
- mut rustc,
- loaded_sysroot,
- cargo_metadata,
- cargo_config_extra_env,
- ) = match join {
- Ok(it) => it,
- Err(e) => std::panic::resume_unwind(e),
- };
+ let (rustc_cfg, data_layout, mut rustc, loaded_sysroot, cargo_metadata, mut cargo_env) =
+ match join {
+ Ok(it) => it,
+ Err(e) => std::panic::resume_unwind(e),
+ };
+
+ for (key, value) in config.extra_env.iter() {
+ if let Some(value) = value {
+ cargo_env.insert(key.clone(), value.clone());
+ }
+ }
let (meta, error) = cargo_metadata.with_context(|| {
format!(
"Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
)
})?;
- let cargo = CargoWorkspace::new(meta, cargo_toml.clone(), cargo_config_extra_env, false);
+ let cargo = CargoWorkspace::new(meta, cargo_toml.clone(), cargo_env, false);
if let Some(loaded_sysroot) = loaded_sysroot {
tracing::info!(src_root = ?sysroot.rust_lib_src_root(), root = %loaded_sysroot, "Loaded sysroot");
sysroot.set_workspace(loaded_sysroot);
@@ -586,7 +586,8 @@
.unwrap_or_else(|| dir.join("target").into());
let cargo_script =
fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| {
- let cargo_config_extra_env = cargo_config_env(detached_file, &config_file);
+ let cargo_config_extra_env =
+ cargo_config_env(detached_file, &config_file, &config.extra_env);
(
CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false),
WorkspaceBuildScripts::default(),
@@ -1089,7 +1090,13 @@
},
file_id,
)| {
- let env = env.clone().into_iter().collect();
+ let mut env = env.clone().into_iter().collect::<Env>();
+ // Override existing env vars with those from `extra_env`
+ env.extend(
+ extra_env
+ .iter()
+ .filter_map(|(k, v)| v.as_ref().map(|v| (k.clone(), v.clone()))),
+ );
let target_cfgs = match target.as_deref() {
Some(target) => cfg_cache.entry(target).or_insert_with(|| {