Skip to content

Commit fd25863

Browse files
committed
Improvements to StringList config handling.
1 parent 5f07972 commit fd25863

File tree

6 files changed

+162
-69
lines changed

6 files changed

+162
-69
lines changed

src/cargo/util/config/de.rs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,28 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
176176
visitor.visit_seq(ConfigSeqAccess::new(self)?)
177177
}
178178

179+
fn deserialize_newtype_struct<V>(
180+
self,
181+
name: &'static str,
182+
visitor: V,
183+
) -> Result<V::Value, Self::Error>
184+
where
185+
V: de::Visitor<'de>,
186+
{
187+
if name == "StringList" {
188+
let vals = self.config.get_list_or_string(&self.key)?;
189+
let vals: Vec<String> = vals.into_iter().map(|vd| vd.0).collect();
190+
visitor.visit_newtype_struct(vals.into_deserializer())
191+
} else {
192+
visitor.visit_newtype_struct(self)
193+
}
194+
}
195+
179196
// These aren't really supported, yet.
180197
serde::forward_to_deserialize_any! {
181198
f32 f64 char str bytes
182199
byte_buf unit unit_struct
183-
enum identifier ignored_any newtype_struct
200+
enum identifier ignored_any
184201
}
185202
}
186203

@@ -345,36 +362,7 @@ impl ConfigSeqAccess {
345362
res.extend(v.val);
346363
}
347364

348-
// Check environment.
349-
if let Some(v) = de.config.env.get(de.key.as_env_key()) {
350-
let def = Definition::Environment(de.key.as_env_key().to_string());
351-
if de.config.cli_unstable().advanced_env && v.starts_with('[') && v.ends_with(']') {
352-
// Parse an environment string as a TOML array.
353-
let toml_s = format!("value={}", v);
354-
let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| {
355-
ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
356-
})?;
357-
let values = toml_v
358-
.as_table()
359-
.unwrap()
360-
.get("value")
361-
.unwrap()
362-
.as_array()
363-
.expect("env var was not array");
364-
for value in values {
365-
// TODO: support other types.
366-
let s = value.as_str().ok_or_else(|| {
367-
ConfigError::new(
368-
format!("expected string, found {}", value.type_str()),
369-
def.clone(),
370-
)
371-
})?;
372-
res.push((s.to_string(), def.clone()));
373-
}
374-
} else {
375-
res.extend(v.split_whitespace().map(|s| (s.to_string(), def.clone())));
376-
}
377-
}
365+
de.config.get_env_list(&de.key, &mut res)?;
378366

379367
Ok(ConfigSeqAccess {
380368
list_iter: res.into_iter(),

src/cargo/util/config/mod.rs

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,70 @@ impl Config {
573573
}
574574
}
575575

576+
/// Helper for StringList type to get something that is a string or list.
577+
fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
578+
let mut res = Vec::new();
579+
match self.get_cv(&key)? {
580+
Some(CV::List(val, _def)) => res.extend(val),
581+
Some(CV::String(val, def)) => {
582+
let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
583+
res.extend(split_vs);
584+
}
585+
Some(val) => {
586+
return self.expected("string or array of strings", key, &val);
587+
}
588+
None => {}
589+
}
590+
591+
self.get_env_list(key, &mut res)?;
592+
Ok(res)
593+
}
594+
595+
/// Internal method for getting an environment variable as a list.
596+
fn get_env_list(
597+
&self,
598+
key: &ConfigKey,
599+
output: &mut Vec<(String, Definition)>,
600+
) -> CargoResult<()> {
601+
let env_val = match self.env.get(key.as_env_key()) {
602+
Some(v) => v,
603+
None => return Ok(()),
604+
};
605+
606+
let def = Definition::Environment(key.as_env_key().to_string());
607+
if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
608+
// Parse an environment string as a TOML array.
609+
let toml_s = format!("value={}", env_val);
610+
let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| {
611+
ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
612+
})?;
613+
let values = toml_v
614+
.as_table()
615+
.unwrap()
616+
.get("value")
617+
.unwrap()
618+
.as_array()
619+
.expect("env var was not array");
620+
for value in values {
621+
// TODO: support other types.
622+
let s = value.as_str().ok_or_else(|| {
623+
ConfigError::new(
624+
format!("expected string, found {}", value.type_str()),
625+
def.clone(),
626+
)
627+
})?;
628+
output.push((s.to_string(), def.clone()));
629+
}
630+
} else {
631+
output.extend(
632+
env_val
633+
.split_whitespace()
634+
.map(|s| (s.to_string(), def.clone())),
635+
);
636+
}
637+
Ok(())
638+
}
639+
576640
/// Low-level method for getting a config value as a `OptValue<HashMap<String, CV>>`.
577641
///
578642
/// NOTE: This does not read from env. The caller is responsible for that.
@@ -1650,43 +1714,11 @@ pub struct CargoBuildConfig {
16501714
/// a = 'a b c'
16511715
/// b = ['a', 'b', 'c']
16521716
/// ```
1653-
#[derive(Debug)]
1654-
pub struct StringList {
1655-
list: Vec<String>,
1656-
}
1717+
#[derive(Debug, Deserialize)]
1718+
pub struct StringList(Vec<String>);
16571719

16581720
impl StringList {
16591721
pub fn as_slice(&self) -> &[String] {
1660-
&self.list
1661-
}
1662-
}
1663-
1664-
impl<'de> serde::de::Deserialize<'de> for StringList {
1665-
fn deserialize<D: serde::de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1666-
#[derive(Deserialize)]
1667-
#[serde(untagged)]
1668-
enum Target {
1669-
String(String),
1670-
List(Vec<String>),
1671-
}
1672-
1673-
// Add some context to the error. Serde gives a vague message for
1674-
// untagged enums. See https://github.com/serde-rs/serde/issues/773
1675-
let result = match Target::deserialize(d) {
1676-
Ok(r) => r,
1677-
Err(e) => {
1678-
return Err(serde::de::Error::custom(format!(
1679-
"failed to deserialize, expected a string or array of strings: {}",
1680-
e
1681-
)))
1682-
}
1683-
};
1684-
1685-
Ok(match result {
1686-
Target::String(s) => StringList {
1687-
list: s.split_whitespace().map(str::to_string).collect(),
1688-
},
1689-
Target::List(list) => StringList { list },
1690-
})
1722+
&self.0
16911723
}
16921724
}

src/cargo/util/config/path.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl<'de> serde::Deserialize<'de> for PathAndArgs {
5656
D: serde::Deserializer<'de>,
5757
{
5858
let vsl = Value::<StringList>::deserialize(deserializer)?;
59-
let mut strings = vsl.val.list;
59+
let mut strings = vsl.val.0;
6060
if strings.is_empty() {
6161
return Err(D::Error::invalid_length(0, &"at least one element"));
6262
}

tests/testsuite/bad_config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,8 +1355,9 @@ Caused by:
13551355
could not load config key `target.cfg(not(target_os = \"none\")).runner`
13561356
13571357
Caused by:
1358-
failed to deserialize, expected a string or array of strings: \
1359-
data did not match any variant of untagged enum Target
1358+
invalid configuration for key `target.cfg(not(target_os = \"none\")).runner`
1359+
expected a string or array of strings, but found a boolean for \
1360+
`target.cfg(not(target_os = \"none\")).runner` in [..]/foo/.cargo/config
13601361
",
13611362
)
13621363
.run();

tests/testsuite/config.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,3 +1205,56 @@ fn overlapping_env_config() {
12051205
assert_eq!(s.debug_assertions, Some(true));
12061206
assert_eq!(s.debug, Some(1));
12071207
}
1208+
1209+
#[cargo_test]
1210+
fn string_list_tricky_env() {
1211+
// Make sure StringList handles typed env values.
1212+
let config = ConfigBuilder::new()
1213+
.env("CARGO_KEY1", "123")
1214+
.env("CARGO_KEY2", "true")
1215+
.env("CARGO_KEY3", "1 2")
1216+
.build();
1217+
let x = config.get::<StringList>("key1").unwrap();
1218+
assert_eq!(x.as_slice(), &["123".to_string()]);
1219+
let x = config.get::<StringList>("key2").unwrap();
1220+
assert_eq!(x.as_slice(), &["true".to_string()]);
1221+
let x = config.get::<StringList>("key3").unwrap();
1222+
assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
1223+
}
1224+
1225+
#[cargo_test]
1226+
fn string_list_wrong_type() {
1227+
// What happens if StringList is given then wrong type.
1228+
write_config("some_list = 123");
1229+
let config = ConfigBuilder::new().build();
1230+
assert_error(
1231+
config.get::<StringList>("some_list").unwrap_err(),
1232+
"\
1233+
invalid configuration for key `some_list`
1234+
expected a string or array of strings, but found a integer for `some_list` in [..]/.cargo/config",
1235+
);
1236+
1237+
write_config("some_list = \"1 2\"");
1238+
let config = ConfigBuilder::new().build();
1239+
let x = config.get::<StringList>("some_list").unwrap();
1240+
assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
1241+
}
1242+
1243+
#[cargo_test]
1244+
fn string_list_advanced_env() {
1245+
// StringList with advanced env.
1246+
let config = ConfigBuilder::new()
1247+
.unstable_flag("advanced-env")
1248+
.env("CARGO_KEY1", "[]")
1249+
.env("CARGO_KEY2", "['1 2', '3']")
1250+
.env("CARGO_KEY3", "[123]")
1251+
.build();
1252+
let x = config.get::<StringList>("key1").unwrap();
1253+
assert_eq!(x.as_slice(), &[] as &[String]);
1254+
let x = config.get::<StringList>("key2").unwrap();
1255+
assert_eq!(x.as_slice(), &["1 2".to_string(), "3".to_string()]);
1256+
assert_error(
1257+
config.get::<StringList>("key3").unwrap_err(),
1258+
"error in environment variable `CARGO_KEY3`: expected string, found integer",
1259+
);
1260+
}

tests/testsuite/tool_paths.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,25 @@ fn custom_runner_env() {
278278
.run();
279279
}
280280

281+
#[cargo_test]
282+
#[cfg(unix)] // Assumes `true` is in PATH.
283+
fn custom_runner_env_true() {
284+
// Check for a bug where "true" was interpreted as a boolean instead of
285+
// the executable.
286+
let target = rustc_host();
287+
let p = project().file("src/main.rs", "fn main() {}").build();
288+
289+
let key = format!(
290+
"CARGO_TARGET_{}_RUNNER",
291+
target.to_uppercase().replace('-', "_")
292+
);
293+
294+
p.cargo("run")
295+
.env(&key, "true")
296+
.with_stderr_contains("[RUNNING] `true target/debug/foo[EXE]`")
297+
.run();
298+
}
299+
281300
#[cargo_test]
282301
fn custom_linker_env() {
283302
let target = rustc_host();

0 commit comments

Comments
 (0)