Store Definition in ConfigValue.

This commit is contained in:
Eric Huss 2019-11-28 14:03:11 -08:00
parent 6d95721ec5
commit 2e5796d4e8
6 changed files with 130 additions and 137 deletions

View File

@ -392,16 +392,17 @@ pub fn add_overrides<'a>(
registry: &mut PackageRegistry<'a>, registry: &mut PackageRegistry<'a>,
ws: &Workspace<'a>, ws: &Workspace<'a>,
) -> CargoResult<()> { ) -> CargoResult<()> {
let paths = match ws.config().get_list("paths")? { let config = ws.config();
let paths = match config.get_list("paths")? {
Some(list) => list, Some(list) => list,
None => return Ok(()), None => return Ok(()),
}; };
let paths = paths.val.iter().map(|&(ref s, ref p)| { let paths = paths.val.iter().map(|(s, def)| {
// The path listed next to the string is the config file in which the // The path listed next to the string is the config file in which the
// key was located, so we want to pop off the `.cargo/config` component // key was located, so we want to pop off the `.cargo/config` component
// to get the directory containing the `.cargo` folder. // to get the directory containing the `.cargo` folder.
(p.parent().unwrap().parent().unwrap().join(s), p) (def.root(config).join(s), def)
}); });
for (path, definition) in paths { for (path, definition) in paths {
@ -412,7 +413,7 @@ pub fn add_overrides<'a>(
"failed to update path override `{}` \ "failed to update path override `{}` \
(defined in `{}`)", (defined in `{}`)",
path.display(), path.display(),
definition.display() definition
) )
})?; })?;
registry.add_override(Box::new(source)); registry.add_override(Box::new(source));

View File

@ -5,7 +5,6 @@ use crate::util::config::{Config, ConfigError, ConfigKey};
use crate::util::config::{ConfigValue as CV, Definition, Value}; use crate::util::config::{ConfigValue as CV, Definition, Value};
use serde::{de, de::IntoDeserializer}; use serde::{de, de::IntoDeserializer};
use std::collections::HashSet; use std::collections::HashSet;
use std::path::PathBuf;
use std::vec; use std::vec;
/// Serde deserializer used to convert config values to a target type using /// Serde deserializer used to convert config values to a target type using
@ -64,18 +63,18 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
let o_cv = self.config.get_cv(&self.key)?; let o_cv = self.config.get_cv(&self.key)?;
if let Some(cv) = o_cv { if let Some(cv) = o_cv {
let res: (Result<V::Value, ConfigError>, PathBuf) = match cv { let res: (Result<V::Value, ConfigError>, Definition) = match cv {
CV::Integer(i, path) => (visitor.visit_i64(i), path), CV::Integer(i, def) => (visitor.visit_i64(i), def),
CV::String(s, path) => (visitor.visit_string(s), path), CV::String(s, def) => (visitor.visit_string(s), def),
CV::List(_, path) => (visitor.visit_seq(ConfigSeqAccess::new(self.clone())?), path), CV::List(_, def) => (visitor.visit_seq(ConfigSeqAccess::new(self.clone())?), def),
CV::Table(_, path) => ( CV::Table(_, def) => (
visitor.visit_map(ConfigMapAccess::new_map(self.clone())?), visitor.visit_map(ConfigMapAccess::new_map(self.clone())?),
path, def,
), ),
CV::Boolean(b, path) => (visitor.visit_bool(b), path), CV::Boolean(b, def) => (visitor.visit_bool(b), def),
}; };
let (res, path) = res; let (res, def) = res;
return res.map_err(|e| e.with_key_context(&self.key, Definition::Path(path))); return res.map_err(|e| e.with_key_context(&self.key, def));
} }
Err(ConfigError::missing(&self.key)) Err(ConfigError::missing(&self.key))
} }
@ -224,10 +223,10 @@ impl<'config> ConfigMapAccess<'config> {
continue; continue;
} }
de.config.shell().warn(format!( de.config.shell().warn(format!(
"unused key `{}.{}` in config file `{}`", "unused key `{}.{}` in config `{}`",
de.key, de.key,
t_key, t_key,
value.definition_path().display() value.definition()
))?; ))?;
} }
} }
@ -285,9 +284,7 @@ impl ConfigSeqAccess {
fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess, ConfigError> { fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess, ConfigError> {
let mut res = Vec::new(); let mut res = Vec::new();
if let Some(v) = de.config._get_list(&de.key)? { if let Some(v) = de.config._get_list(&de.key)? {
for (s, path) in v.val { res.extend(v.val);
res.push((s, Definition::Path(path)));
}
} }
if de.config.cli_unstable().advanced_env { if de.config.cli_unstable().advanced_env {
@ -366,7 +363,7 @@ impl<'config> ValueDeserializer<'config> {
Definition::Environment(env.to_string()) Definition::Environment(env.to_string())
} else { } else {
match de.config.get_cv(&de.key)? { match de.config.get_cv(&de.key)? {
Some(val) => Definition::Path(val.definition_path().to_path_buf()), Some(val) => val.definition().clone(),
None => { None => {
return Err(failure::format_err!( return Err(failure::format_err!(
"failed to find definition of `{}`", "failed to find definition of `{}`",

View File

@ -376,17 +376,17 @@ impl Config {
None => return Ok(None), None => return Ok(None),
}; };
for (i, part) in parts { for (i, part) in parts {
match *val { match val {
CV::Table(ref map, _) => { CV::Table(map, _) => {
val = match map.get(part) { val = match map.get(part) {
Some(val) => val, Some(val) => val,
None => return Ok(None), None => return Ok(None),
} }
} }
CV::Integer(_, ref path) CV::Integer(_, def)
| CV::String(_, ref path) | CV::String(_, def)
| CV::List(_, ref path) | CV::List(_, def)
| CV::Boolean(_, ref path) => { | CV::Boolean(_, def) => {
let key_so_far: Vec<&str> = key.parts().take(i).collect(); let key_so_far: Vec<&str> = key.parts().take(i).collect();
bail!( bail!(
"expected table for configuration key `{}`, \ "expected table for configuration key `{}`, \
@ -394,7 +394,7 @@ impl Config {
// This join doesn't handle quoting properly. // This join doesn't handle quoting properly.
key_so_far.join("."), key_so_far.join("."),
val.desc(), val.desc(),
path.display() def
) )
} }
} }
@ -456,10 +456,7 @@ impl Config {
None => { None => {
let o_cv = self.get_cv(key)?; let o_cv = self.get_cv(key)?;
match o_cv { match o_cv {
Some(CV::String(s, path)) => Ok(Some(Value { Some(CV::String(val, definition)) => Ok(Some(Value { val, definition })),
val: s,
definition: Definition::Path(path),
})),
Some(cv) => Err(ConfigError::expected(key, "a string", &cv)), Some(cv) => Err(ConfigError::expected(key, "a string", &cv)),
None => Ok(None), None => Ok(None),
} }
@ -474,10 +471,7 @@ impl Config {
None => { None => {
let o_cv = self.get_cv(key)?; let o_cv = self.get_cv(key)?;
match o_cv { match o_cv {
Some(CV::Boolean(b, path)) => Ok(Some(Value { Some(CV::Boolean(val, definition)) => Ok(Some(Value { val, definition })),
val: b,
definition: Definition::Path(path),
})),
Some(cv) => Err(ConfigError::expected(key, "true/false", &cv)), Some(cv) => Err(ConfigError::expected(key, "true/false", &cv)),
None => Ok(None), None => Ok(None),
} }
@ -512,17 +506,14 @@ impl Config {
/// ///
/// NOTE: this does **not** support environment variables. Use `get` instead /// NOTE: this does **not** support environment variables. Use `get` instead
/// if you want that. /// if you want that.
pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, PathBuf)>>> { pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
let key = ConfigKey::from_str(key); let key = ConfigKey::from_str(key);
self._get_list(&key) self._get_list(&key)
} }
fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, PathBuf)>>> { fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
match self.get_cv(&key)? { match self.get_cv(&key)? {
Some(CV::List(i, path)) => Ok(Some(Value { Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
val: i,
definition: Definition::Path(path),
})),
Some(val) => self.expected("list", key, &val), Some(val) => self.expected("list", key, &val),
None => Ok(None), None => Ok(None),
} }
@ -533,10 +524,7 @@ impl Config {
/// NOTE: This does not read from env. The caller is responsible for that. /// NOTE: This does not read from env. The caller is responsible for that.
fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> { fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
match self.get_cv(key)? { match self.get_cv(key)? {
Some(CV::Table(i, path)) => Ok(Some(Value { Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
val: i,
definition: Definition::Path(path),
})),
Some(val) => self.expected("table", key, &val), Some(val) => self.expected("table", key, &val),
None => Ok(None), None => Ok(None),
} }
@ -547,10 +535,7 @@ impl Config {
match self.get_env::<i64>(key)? { match self.get_env::<i64>(key)? {
Some(v) => Ok(Some(v)), Some(v) => Ok(Some(v)),
None => match self.get_cv(key)? { None => match self.get_cv(key)? {
Some(CV::Integer(i, path)) => Ok(Some(Value { Some(CV::Integer(val, definition)) => Ok(Some(Value { val, definition })),
val: i,
definition: Definition::Path(path),
})),
Some(cv) => Err(ConfigError::expected(key, "an integer", &cv)), Some(cv) => Err(ConfigError::expected(key, "an integer", &cv)),
None => Ok(None), None => Ok(None),
}, },
@ -674,7 +659,7 @@ impl Config {
fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> { fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
// This definition path is ignored, this is just a temporary container // This definition path is ignored, this is just a temporary container
// representing the entire file. // representing the entire file.
let mut cfg = CV::Table(HashMap::new(), PathBuf::from(".")); let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
let home = self.home_path.clone().into_path_unlocked(); let home = self.home_path.clone().into_path_unlocked();
self.walk_tree(path, &home, |path| { self.walk_tree(path, &home, |path| {
@ -685,13 +670,14 @@ impl Config {
let toml = cargo_toml::parse(&contents, path, self).chain_err(|| { let toml = cargo_toml::parse(&contents, path, self).chain_err(|| {
format!("could not parse TOML configuration in `{}`", path.display()) format!("could not parse TOML configuration in `{}`", path.display())
})?; })?;
let value = CV::from_toml(path, toml).chain_err(|| { let value =
format!( CV::from_toml(Definition::Path(path.to_path_buf()), toml).chain_err(|| {
"failed to load TOML configuration from `{}`", format!(
path.display() "failed to load TOML configuration from `{}`",
) path.display()
})?; )
cfg.merge(value) })?;
cfg.merge(value, false)
.chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?; .chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?;
Ok(()) Ok(())
}) })
@ -832,33 +818,32 @@ impl Config {
) )
})?; })?;
let mut value = CV::from_toml(&credentials, toml).chain_err(|| { let mut value =
format!( CV::from_toml(Definition::Path(credentials.clone()), toml).chain_err(|| {
"failed to load TOML configuration from `{}`", format!(
credentials.display() "failed to load TOML configuration from `{}`",
) credentials.display()
})?; )
})?;
// Backwards compatibility for old `.cargo/credentials` layout. // Backwards compatibility for old `.cargo/credentials` layout.
{ {
let value = match value { let (value_map, def) = match value {
CV::Table(ref mut value, _) => value, CV::Table(ref mut value, ref def) => (value, def),
_ => unreachable!(), _ => unreachable!(),
}; };
if let Some(token) = value.remove("token") { if let Some(token) = value_map.remove("token") {
if let Vacant(entry) = value.entry("registry".into()) { if let Vacant(entry) = value_map.entry("registry".into()) {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("token".into(), token); map.insert("token".into(), token);
let table = CV::Table(map, PathBuf::from(".")); let table = CV::Table(map, def.clone());
entry.insert(table); entry.insert(table);
} }
} }
} }
// We want value to override `cfg`, so swap these. cfg.merge(value, true)?;
mem::swap(cfg, &mut value);
cfg.merge(value)?;
Ok(()) Ok(())
} }
@ -1068,7 +1053,7 @@ impl ConfigError {
expected, expected,
found.desc() found.desc()
), ),
definition: Some(Definition::Path(found.definition_path().to_path_buf())), definition: Some(found.definition().clone()),
} }
} }
@ -1124,58 +1109,58 @@ impl From<failure::Error> for ConfigError {
#[derive(Eq, PartialEq, Clone)] #[derive(Eq, PartialEq, Clone)]
pub enum ConfigValue { pub enum ConfigValue {
Integer(i64, PathBuf), Integer(i64, Definition),
String(String, PathBuf), String(String, Definition),
List(Vec<(String, PathBuf)>, PathBuf), List(Vec<(String, Definition)>, Definition),
Table(HashMap<String, ConfigValue>, PathBuf), Table(HashMap<String, ConfigValue>, Definition),
Boolean(bool, PathBuf), Boolean(bool, Definition),
} }
impl fmt::Debug for ConfigValue { impl fmt::Debug for ConfigValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match self {
CV::Integer(i, ref path) => write!(f, "{} (from {})", i, path.display()), CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
CV::Boolean(b, ref path) => write!(f, "{} (from {})", b, path.display()), CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
CV::String(ref s, ref path) => write!(f, "{} (from {})", s, path.display()), CV::String(s, def) => write!(f, "{} (from {})", s, def),
CV::List(ref list, ref path) => { CV::List(list, def) => {
write!(f, "[")?; write!(f, "[")?;
for (i, &(ref s, ref path)) in list.iter().enumerate() { for (i, (s, def)) in list.iter().enumerate() {
if i > 0 { if i > 0 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{} (from {})", s, path.display())?; write!(f, "{} (from {})", s, def)?;
} }
write!(f, "] (from {})", path.display()) write!(f, "] (from {})", def)
} }
CV::Table(ref table, _) => write!(f, "{:?}", table), CV::Table(table, _) => write!(f, "{:?}", table),
} }
} }
} }
impl ConfigValue { impl ConfigValue {
fn from_toml(path: &Path, toml: toml::Value) -> CargoResult<ConfigValue> { fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
match toml { match toml {
toml::Value::String(val) => Ok(CV::String(val, path.to_path_buf())), toml::Value::String(val) => Ok(CV::String(val, def)),
toml::Value::Boolean(b) => Ok(CV::Boolean(b, path.to_path_buf())), toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
toml::Value::Integer(i) => Ok(CV::Integer(i, path.to_path_buf())), toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
toml::Value::Array(val) => Ok(CV::List( toml::Value::Array(val) => Ok(CV::List(
val.into_iter() val.into_iter()
.map(|toml| match toml { .map(|toml| match toml {
toml::Value::String(val) => Ok((val, path.to_path_buf())), toml::Value::String(val) => Ok((val, def.clone())),
v => bail!("expected string but found {} in list", v.type_str()), v => bail!("expected string but found {} in list", v.type_str()),
}) })
.collect::<CargoResult<_>>()?, .collect::<CargoResult<_>>()?,
path.to_path_buf(), def,
)), )),
toml::Value::Table(val) => Ok(CV::Table( toml::Value::Table(val) => Ok(CV::Table(
val.into_iter() val.into_iter()
.map(|(key, value)| { .map(|(key, value)| {
let value = CV::from_toml(path, value) let value = CV::from_toml(def.clone(), value)
.chain_err(|| format!("failed to parse key `{}`", key))?; .chain_err(|| format!("failed to parse key `{}`", key))?;
Ok((key, value)) Ok((key, value))
}) })
.collect::<CargoResult<_>>()?, .collect::<CargoResult<_>>()?,
path.to_path_buf(), def,
)), )),
v => bail!( v => bail!(
"found TOML configuration value of unknown type `{}`", "found TOML configuration value of unknown type `{}`",
@ -1206,7 +1191,7 @@ impl ConfigValue {
/// Container types (tables and arrays) are merged with existing values. /// Container types (tables and arrays) are merged with existing values.
/// ///
/// Container and non-container types cannot be mixed. /// Container and non-container types cannot be mixed.
fn merge(&mut self, from: ConfigValue) -> CargoResult<()> { fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
match (self, from) { match (self, from) {
(&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => { (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
let new = mem::replace(new, Vec::new()); let new = mem::replace(new, Vec::new());
@ -1217,17 +1202,15 @@ impl ConfigValue {
for (key, value) in new { for (key, value) in new {
match old.entry(key.clone()) { match old.entry(key.clone()) {
Occupied(mut entry) => { Occupied(mut entry) => {
let path = value.definition_path().to_path_buf(); let new_def = value.definition().clone();
let entry = entry.get_mut(); let entry = entry.get_mut();
entry.merge(value).chain_err(|| { entry.merge(value, force).chain_err(|| {
format!( format!(
"failed to merge key `{}` between \ "failed to merge key `{}` between \
files:\n \ {} and {}",
file 1: {}\n \
file 2: {}",
key, key,
entry.definition_path().display(), entry.definition(),
path.display() new_def,
) )
})?; })?;
} }
@ -1248,43 +1231,47 @@ impl ConfigValue {
found.desc() found.desc()
))); )));
} }
_ => {} (old, mut new) => {
if force || new.definition().is_higher_priority(old.definition()) {
mem::swap(old, &mut new);
}
}
} }
Ok(()) Ok(())
} }
pub fn i64(&self, key: &str) -> CargoResult<(i64, &Path)> { pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
match *self { match self {
CV::Integer(i, ref p) => Ok((i, p)), CV::Integer(i, def) => Ok((*i, def)),
_ => self.expected("integer", key), _ => self.expected("integer", key),
} }
} }
pub fn string(&self, key: &str) -> CargoResult<(&str, &Path)> { pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
match *self { match self {
CV::String(ref s, ref p) => Ok((s, p)), CV::String(s, def) => Ok((s, def)),
_ => self.expected("string", key), _ => self.expected("string", key),
} }
} }
pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Path)> { pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
match *self { match self {
CV::Table(ref table, ref p) => Ok((table, p)), CV::Table(table, def) => Ok((table, def)),
_ => self.expected("table", key), _ => self.expected("table", key),
} }
} }
pub fn list(&self, key: &str) -> CargoResult<&[(String, PathBuf)]> { pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
match *self { match self {
CV::List(ref list, _) => Ok(list), CV::List(list, _) => Ok(list),
_ => self.expected("list", key), _ => self.expected("list", key),
} }
} }
pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Path)> { pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
match *self { match self {
CV::Boolean(b, ref p) => Ok((b, p)), CV::Boolean(b, def) => Ok((*b, def)),
_ => self.expected("bool", key), _ => self.expected("bool", key),
} }
} }
@ -1299,13 +1286,13 @@ impl ConfigValue {
} }
} }
pub fn definition_path(&self) -> &Path { pub fn definition(&self) -> &Definition {
match *self { match self {
CV::Boolean(_, ref p) CV::Boolean(_, def)
| CV::Integer(_, ref p) | CV::Integer(_, def)
| CV::String(_, ref p) | CV::String(_, def)
| CV::List(_, ref p) | CV::List(_, def)
| CV::Table(_, ref p) => p, | CV::Table(_, def) => def,
} }
} }
@ -1315,7 +1302,7 @@ impl ConfigValue {
wanted, wanted,
self.desc(), self.desc(),
key, key,
self.definition_path().display() self.definition()
) )
} }
} }
@ -1345,17 +1332,17 @@ pub fn save_credentials(cfg: &Config, token: String, registry: Option<String>) -
let (key, mut value) = { let (key, mut value) = {
let key = "token".to_string(); let key = "token".to_string();
let value = ConfigValue::String(token, file.path().to_path_buf()); let value = ConfigValue::String(token, Definition::Path(file.path().to_path_buf()));
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(key, value); map.insert(key, value);
let table = CV::Table(map, file.path().to_path_buf()); let table = CV::Table(map, Definition::Path(file.path().to_path_buf()));
if let Some(registry) = registry.clone() { if let Some(registry) = registry.clone() {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(registry, table); map.insert(registry, table);
( (
"registries".into(), "registries".into(),
CV::Table(map, file.path().to_path_buf()), CV::Table(map, Definition::Path(file.path().to_path_buf())),
) )
} else { } else {
("registry".into(), table) ("registry".into(), table)

View File

@ -51,7 +51,7 @@ pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition";
pub(crate) const NAME: &str = "$__cargo_private_Value"; pub(crate) const NAME: &str = "$__cargo_private_Value";
pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD]; pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD];
#[derive(Clone, Debug)] #[derive(Clone, Debug, Eq)]
pub enum Definition { pub enum Definition {
Path(PathBuf), Path(PathBuf),
Environment(String), Environment(String),
@ -64,6 +64,14 @@ impl Definition {
Definition::Environment(_) => config.cwd(), Definition::Environment(_) => config.cwd(),
} }
} }
pub fn is_higher_priority(&self, other: &Definition) -> bool {
// environment > path
match (self, other) {
(Definition::Environment(_), Definition::Path(_)) => true,
_ => false,
}
}
} }
impl PartialEq for Definition { impl PartialEq for Definition {
@ -71,7 +79,7 @@ impl PartialEq for Definition {
// configuration values are equivalent no matter where they're defined, // configuration values are equivalent no matter where they're defined,
// but they need to be defined in the same location. For example if // but they need to be defined in the same location. For example if
// they're defined in the environment that's different than being // they're defined in the environment that's different than being
// defined in a file due to path interepretations. // defined in a file due to path interpretations.
mem::discriminant(self) == mem::discriminant(other) mem::discriminant(self) == mem::discriminant(other)
} }
} }

View File

@ -305,7 +305,7 @@ unused = 456
let path = paths::root().join("shell.out"); let path = paths::root().join("shell.out");
let output = fs::read_to_string(path).unwrap(); let output = fs::read_to_string(path).unwrap();
let expected = "\ let expected = "\
warning: unused key `S.unused` in config file `[..]/.cargo/config` warning: unused key `S.unused` in config `[..]/.cargo/config`
"; ";
if !lines_match(expected, &output) { if !lines_match(expected, &output) {
panic!( panic!(

View File

@ -56,11 +56,11 @@ fn profile_config_validate_warnings() {
.masquerade_as_nightly_cargo() .masquerade_as_nightly_cargo()
.with_stderr_unordered( .with_stderr_unordered(
"\ "\
[WARNING] unused key `profile.asdf` in config file `[..].cargo/config` [WARNING] unused key `profile.asdf` in config `[..].cargo/config`
[WARNING] unused key `profile.test` in config file `[..].cargo/config` [WARNING] unused key `profile.test` in config `[..].cargo/config`
[WARNING] unused key `profile.dev.bad-key` in config file `[..].cargo/config` [WARNING] unused key `profile.dev.bad-key` in config `[..].cargo/config`
[WARNING] unused key `profile.dev.package.bar.bad-key-bar` in config file `[..].cargo/config` [WARNING] unused key `profile.dev.package.bar.bad-key-bar` in config `[..].cargo/config`
[WARNING] unused key `profile.dev.build-override.bad-key-bo` in config file `[..].cargo/config` [WARNING] unused key `profile.dev.build-override.bad-key-bo` in config `[..].cargo/config`
[COMPILING] foo [..] [COMPILING] foo [..]
[FINISHED] [..] [FINISHED] [..]
", ",