diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 635063cd5..4d2d94cb6 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -334,68 +334,32 @@ impl Features { /// /// If you have any trouble with this, please let us know! #[derive(Default, Debug, Deserialize)] -#[serde(rename_all="kebab-case")] +#[serde(default, rename_all = "kebab-case")] pub struct CliUnstable { - #[serde(deserialize_with="default_false")] pub print_im_a_teapot: bool, - #[serde(deserialize_with="default_false")] pub unstable_options: bool, - #[serde(deserialize_with="default_false")] pub no_index_update: bool, - #[serde(deserialize_with="default_false")] pub avoid_dev_deps: bool, - #[serde(deserialize_with="default_false")] pub minimal_versions: bool, - #[serde(deserialize_with="default_false")] pub package_features: bool, - #[serde(deserialize_with="default_false")] pub advanced_env: bool, - #[serde(deserialize_with="default_false")] pub config_include: bool, - #[serde(deserialize_with="default_false")] pub dual_proc_macros: bool, - #[serde(deserialize_with="default_false")] pub mtime_on_use: bool, - #[serde(deserialize_with="default_false")] pub named_profiles: bool, - #[serde(deserialize_with="default_false")] pub binary_dep_depinfo: bool, pub build_std: Option>, pub timings: Option>, - #[serde(deserialize_with="default_false")] pub doctest_xcompile: bool, - #[serde(deserialize_with="default_false")] pub panic_abort_tests: bool, - #[serde(deserialize_with="default_false")] pub jobserver_per_rustc: bool, pub features: Option>, - #[serde(deserialize_with="default_false")] pub crate_versions: bool, - #[serde(deserialize_with="default_false")] pub separate_nightlies: bool, - #[serde(deserialize_with="default_false")] pub multitarget: bool, - #[serde(deserialize_with="default_false")] pub rustdoc_map: bool, } -/// Treat boolean Zflags as optionals for deserializing them. -/// This allows missing settings to default to disabled (effectively recreating -/// the serde `default` behavior). Our Deserializer merges multiple sources -/// inline, so the serde facilities for handling missing or additional fields -/// don't quite fit. -/// -/// TODO: This limitation can likely be removed by refactoring -/// `de::ConfigMapAccess` to iterate the union of the config and env sets. -fn default_false<'de, D, T>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, - T: Deserialize<'de> + Default, -{ - let option = Option::deserialize(deserializer)?; - Ok(option.unwrap_or_default()) -} - impl CliUnstable { pub fn parse(&mut self, flags: &[String]) -> CargoResult<()> { if !flags.is_empty() && !nightly_features_allowed() { diff --git a/src/cargo/util/config/de.rs b/src/cargo/util/config/de.rs index 1564a60f0..311ba4116 100644 --- a/src/cargo/util/config/de.rs +++ b/src/cargo/util/config/de.rs @@ -4,6 +4,7 @@ use crate::util::config::value; use crate::util::config::{Config, ConfigError, ConfigKey}; use crate::util::config::{ConfigValue as CV, Definition, Value}; use serde::{de, de::IntoDeserializer}; +use std::collections::HashSet; use std::vec; /// Serde deserializer used to convert config values to a target type using @@ -269,37 +270,54 @@ impl<'config> ConfigMapAccess<'config> { fn new_struct( de: Deserializer<'config>, - fields: &'static [&'static str], + given_fields: &'static [&'static str], ) -> Result, ConfigError> { - let fields: Vec = fields - .iter() - .map(|field| KeyKind::Normal(field.to_string())) - .collect(); + let table = de.config.get_table(&de.key)?; // Assume that if we're deserializing a struct it exhaustively lists all // possible fields on this key that we're *supposed* to use, so take // this opportunity to warn about any keys that aren't recognized as // fields and warn about them. - if let Some(mut v) = de.config.get_table(&de.key)? { - for (t_key, value) in v.val.drain() { - if fields.iter().any(|k| match k { - KeyKind::Normal(s) => s == &t_key, - KeyKind::CaseSensitive(s) => s == &t_key, - }) { - continue; - } + if let Some(v) = table.as_ref() { + let unused_keys = v + .val + .iter() + .filter(|(k, _v)| !given_fields.iter().any(|gk| gk == k)); + for (unused_key, unused_value) in unused_keys { de.config.shell().warn(format!( "unused config key `{}.{}` in `{}`", de.key, - t_key, - value.definition() + unused_key, + unused_value.definition() ))?; } } + let mut fields = HashSet::new(); + + // If the caller is interested in a field which we can provide from + // the environment, get it from there. + for field in given_fields { + let mut field_key = de.key.clone(); + field_key.push(field); + for env_key in de.config.env.keys() { + if env_key.starts_with(field_key.as_env_key()) { + fields.insert(KeyKind::Normal(field.to_string())); + } + } + } + + // Add everything from the config table we're interested in that we + // haven't already provided via an environment variable + if let Some(v) = table { + for key in v.val.keys() { + fields.insert(KeyKind::Normal(key.clone())); + } + } + Ok(ConfigMapAccess { de, - fields, + fields: fields.into_iter().collect(), field_index: 0, }) } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 6dc4a4098..4bb1d7f4d 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -394,7 +394,7 @@ impl<'de> de::Deserialize<'de> for U32OrBool { } #[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[serde(default, rename_all = "kebab-case")] pub struct TomlProfile { pub opt_level: Option, pub lto: Option,