Skip to content

Commit ea8c16c

Browse files
Merge pull request #21995 from smihica/fix/cargo-config-env-override
Fix [env] in .cargo/config.toml overriding process environment variables
2 parents 8c5af72 + 057a2f6 commit ea8c16c

1 file changed

Lines changed: 87 additions & 32 deletions

File tree

  • crates/project-model/src

crates/project-model/src/env.rs

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,31 @@ pub(crate) fn cargo_config_env(
7979
for (key, entry) in env_toml {
8080
let key = key.as_ref().as_ref();
8181
let value = match entry.as_ref() {
82-
DeValue::String(s) => String::from(s.clone()),
82+
DeValue::String(s) => {
83+
// Plain string entries have no `force` option, so they should not
84+
// override existing environment variables (matching Cargo behavior).
85+
if extra_env.get(key).is_some_and(Option::is_some) {
86+
continue;
87+
}
88+
if let Ok(val) = std::env::var(key) { val } else { String::from(s.clone()) }
89+
}
8390
DeValue::Table(entry) => {
8491
// Each entry MUST have a `value` key.
8592
let Some(map) = entry.get("value").and_then(|v| v.as_ref().as_str()) else {
8693
continue;
8794
};
88-
// If the entry already exists in the environment AND the `force` key is not set to
89-
// true, then don't overwrite the value.
90-
if extra_env.get(key).is_some_and(Option::is_some)
91-
&& !entry.get("force").and_then(|v| v.as_ref().as_bool()).unwrap_or(false)
92-
{
93-
continue;
95+
let is_forced =
96+
entry.get("force").and_then(|v| v.as_ref().as_bool()).unwrap_or(false);
97+
// If the entry already exists in the environment AND the `force` key is not set
98+
// to true, use the existing value instead of the config value.
99+
if !is_forced {
100+
if extra_env.get(key).is_some_and(Option::is_some) {
101+
continue;
102+
}
103+
if let Ok(val) = std::env::var(key) {
104+
env.insert(key, val);
105+
continue;
106+
}
94107
}
95108

96109
if let Some(base) = entry.get("relative").and_then(|v| {
@@ -124,38 +137,80 @@ fn parse_output_cargo_config_env_works() {
124137
.unwrap();
125138
let config_path = cwd.join(".cargo").join("config.toml");
126139
let raw = r#"
127-
env.CARGO_WORKSPACE_DIR.relative = true
128-
env.CARGO_WORKSPACE_DIR.value = ""
129-
env.INVALID.relative = "invalidbool"
130-
env.INVALID.value = "../relative"
131-
env.RELATIVE.relative = true
132-
env.RELATIVE.value = "../relative"
133-
env.TEST.value = "test"
134-
env.FORCED.value = "test"
135-
env.FORCED.force = true
136-
env.UNFORCED.value = "test"
137-
env.UNFORCED.forced = false
138-
env.OVERWRITTEN.value = "test"
139-
env.NOT_AN_OBJECT = "value"
140+
env.RA_TEST_WORKSPACE_DIR.relative = true
141+
env.RA_TEST_WORKSPACE_DIR.value = ""
142+
env.RA_TEST_INVALID.relative = "invalidbool"
143+
env.RA_TEST_INVALID.value = "../relative"
144+
env.RA_TEST_RELATIVE.relative = true
145+
env.RA_TEST_RELATIVE.value = "../relative"
146+
env.RA_TEST_UNSET.value = "test"
147+
env.RA_TEST_FORCED.value = "test"
148+
env.RA_TEST_FORCED.force = true
149+
env.RA_TEST_UNFORCED.value = "test"
150+
env.RA_TEST_UNFORCED.forced = false
151+
env.RA_TEST_OVERWRITTEN.value = "test"
152+
env.RA_TEST_NOT_AN_OBJECT = "value"
140153
"#;
141154
let raw = raw.lines().map(|l| format!("{l} # {config_path}")).join("\n");
142155
let config = CargoConfigFile::from_string_for_test(raw);
143156
let extra_env = [
144-
("FORCED", Some("ignored")),
145-
("UNFORCED", Some("newvalue")),
146-
("OVERWRITTEN", Some("newvalue")),
147-
("TEST", None),
157+
("RA_TEST_FORCED", Some("ignored")),
158+
("RA_TEST_UNFORCED", Some("newvalue")),
159+
("RA_TEST_OVERWRITTEN", Some("newvalue")),
160+
("RA_TEST_UNSET", None),
148161
]
149162
.iter()
150163
.map(|(k, v)| (k.to_string(), v.map(ToString::to_string)))
151164
.collect();
152165
let env = cargo_config_env(&Some(config), &extra_env);
153-
assert_eq!(env.get("CARGO_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str()));
154-
assert_eq!(env.get("RELATIVE").as_deref(), Some(cwd.join("../relative").as_str()));
155-
assert_eq!(env.get("INVALID").as_deref(), Some("../relative"));
156-
assert_eq!(env.get("TEST").as_deref(), Some("test"));
157-
assert_eq!(env.get("FORCED").as_deref(), Some("test"));
158-
assert_eq!(env.get("UNFORCED").as_deref(), Some("newvalue"));
159-
assert_eq!(env.get("OVERWRITTEN").as_deref(), Some("newvalue"));
160-
assert_eq!(env.get("NOT_AN_OBJECT").as_deref(), Some("value"));
166+
assert_eq!(env.get("RA_TEST_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str()));
167+
assert_eq!(env.get("RA_TEST_RELATIVE").as_deref(), Some(cwd.join("../relative").as_str()));
168+
assert_eq!(env.get("RA_TEST_INVALID").as_deref(), Some("../relative"));
169+
assert_eq!(env.get("RA_TEST_UNSET").as_deref(), Some("test"));
170+
assert_eq!(env.get("RA_TEST_FORCED").as_deref(), Some("test"));
171+
assert_eq!(env.get("RA_TEST_UNFORCED").as_deref(), Some("newvalue"));
172+
assert_eq!(env.get("RA_TEST_OVERWRITTEN").as_deref(), Some("newvalue"));
173+
assert_eq!(env.get("RA_TEST_NOT_AN_OBJECT").as_deref(), Some("value"));
174+
}
175+
176+
#[test]
177+
fn cargo_config_env_respects_process_env() {
178+
use itertools::Itertools;
179+
180+
let cwd = paths::AbsPathBuf::try_from(
181+
paths::Utf8PathBuf::try_from(std::env::current_dir().unwrap()).unwrap(),
182+
)
183+
.unwrap();
184+
let config_path = cwd.join(".cargo").join("config.toml");
185+
186+
// SAFETY: this test is not run in parallel with other tests that depend on these env vars.
187+
unsafe {
188+
std::env::set_var("RA_TEST_PROCESS_ENV_STRING", "from_process");
189+
std::env::set_var("RA_TEST_PROCESS_ENV_TABLE", "from_process");
190+
std::env::set_var("RA_TEST_PROCESS_ENV_FORCED", "from_process");
191+
}
192+
193+
let raw = r#"
194+
env.RA_TEST_PROCESS_ENV_STRING = "from_config"
195+
env.RA_TEST_PROCESS_ENV_TABLE.value = "from_config"
196+
env.RA_TEST_PROCESS_ENV_FORCED.value = "from_config"
197+
env.RA_TEST_PROCESS_ENV_FORCED.force = true
198+
"#;
199+
let raw = raw.lines().map(|l| format!("{l} # {config_path}")).join("\n");
200+
let config = CargoConfigFile::from_string_for_test(raw);
201+
let extra_env = FxHashMap::default();
202+
let env = cargo_config_env(&Some(config), &extra_env);
203+
204+
// Plain string form should use process env value, not config value
205+
assert_eq!(env.get("RA_TEST_PROCESS_ENV_STRING").as_deref(), Some("from_process"));
206+
// Table form without force should use process env value, not config value
207+
assert_eq!(env.get("RA_TEST_PROCESS_ENV_TABLE").as_deref(), Some("from_process"));
208+
// Table form with force=true should override process env
209+
assert_eq!(env.get("RA_TEST_PROCESS_ENV_FORCED").as_deref(), Some("from_config"));
210+
211+
unsafe {
212+
std::env::remove_var("RA_TEST_PROCESS_ENV_STRING");
213+
std::env::remove_var("RA_TEST_PROCESS_ENV_TABLE");
214+
std::env::remove_var("RA_TEST_PROCESS_ENV_FORCED");
215+
}
161216
}

0 commit comments

Comments
 (0)