diff --git a/Cargo.lock b/Cargo.lock index 498843507b..511090b33e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2090,6 +2090,7 @@ dependencies = [ "scopeguard", "semver", "serde", + "serde_ignored", "sha2", "sharded-slab", "snapbox", @@ -2219,6 +2220,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" diff --git a/Cargo.toml b/Cargo.toml index e5053774aa..4562b6c944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ rustls-platform-verifier = { version = "0.7", optional = true } same-file = "1" semver = "1.0" serde = { version = "1.0", features = ["derive"] } +serde_ignored = "0.1" sha2 = "0.11" sharded-slab = "0.1.1" strsim = "0.11" diff --git a/doc/user-guide/src/overrides.md b/doc/user-guide/src/overrides.md index 849ed515d2..2d13bf6911 100644 --- a/doc/user-guide/src/overrides.md +++ b/doc/user-guide/src/overrides.md @@ -114,6 +114,11 @@ issues with dependencies. ### Toolchain file settings +Rustup reports unrecognized settings as warnings. It ignores them when other +valid settings are present, so these warnings can indicate a misspelling that +would otherwise leave part of the requested configuration unapplied. A future +rustup release will reject unrecognized settings. + #### channel The `channel` setting specifies which [toolchain] to use. The value is a diff --git a/src/config.rs b/src/config.rs index 84e267ea42..2829cede1a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -688,13 +688,25 @@ impl<'a> Cfg<'a> { if let Ok(contents) = contents { // XXX Should not return the unvalidated contents; but a new // internal only safe struct - let override_file = - Cfg::parse_override_file(contents, parse_mode).with_context(|| { + let (override_file, unknown_keys) = Cfg::parse_override_file(contents, parse_mode) + .with_context(|| RustupError::ParsingFile { + name: "override", + path: toolchain_file.clone(), + })?; + for key in unknown_keys { + warn!( + "unknown key `{key}` in '{}'; it is currently ignored, but will become an error in a future release", + toolchain_file.display() + ); + } + if override_file.is_empty() { + return Err(anyhow!(OverrideFileConfigError::Invalid)).with_context(|| { RustupError::ParsingFile { name: "override", path: toolchain_file.clone(), } - })?; + }); + } if let Some(toolchain_name_str) = &override_file.toolchain.channel { let toolchain_name = ResolvableToolchainName::try_from( toolchain_name_str.as_str(), @@ -747,7 +759,7 @@ impl<'a> Cfg<'a> { fn parse_override_file>( contents: S, parse_mode: ParseMode, - ) -> Result { + ) -> Result<(OverrideFile, Vec)> { let contents = contents.as_ref(); match (contents.lines().count(), parse_mode) { @@ -758,18 +770,20 @@ impl<'a> Cfg<'a> { if channel.is_empty() { Err(anyhow!(OverrideFileConfigError::Empty)) } else { - Ok(channel.into()) + Ok((channel.into(), Vec::new())) } } _ => { - let override_file = toml::from_str::(contents) + let deserializer = toml::Deserializer::parse(contents) + .context(OverrideFileConfigError::Parsing)?; + let mut unknown_keys = Vec::new(); + let override_file: OverrideFile = + serde_ignored::deserialize(deserializer, |path| { + unknown_keys.push(path.to_string()); + }) .context(OverrideFileConfigError::Parsing)?; - if override_file.is_empty() { - Err(anyhow!(OverrideFileConfigError::Invalid)) - } else { - Ok(override_file) - } + Ok((override_file, unknown_keys)) } } } @@ -1219,7 +1233,7 @@ mod tests { let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: Some(contents.into()), @@ -1243,7 +1257,7 @@ profile = "default" let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), @@ -1267,7 +1281,7 @@ channel = "nightly-2020-07-10" let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), @@ -1288,7 +1302,7 @@ path = "foobar" let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: None, @@ -1310,7 +1324,7 @@ components = [] let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), @@ -1332,7 +1346,7 @@ targets = [] let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), @@ -1353,7 +1367,7 @@ components = [ "rustfmt" ] let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( - result.unwrap(), + result.unwrap().0, OverrideFile { toolchain: ToolchainSection { channel: None, @@ -1372,11 +1386,23 @@ components = [ "rustfmt" ] [toolchain] "#; - let result = Cfg::parse_override_file(contents, ParseMode::Both); - assert!(matches!( - result.unwrap_err().downcast::(), - Ok(OverrideFileConfigError::Invalid) - )); + let (override_file, unknown_keys) = + Cfg::parse_override_file(contents, ParseMode::Both).unwrap(); + assert!(override_file.is_empty()); + assert!(unknown_keys.is_empty()); + } + + #[test] + fn parse_toml_toolchain_file_collects_unknown_keys() { + let contents = r#"[toolchain] +channel = "nightly" +channnel = "stable" +"#; + + let (override_file, unknown_keys) = + Cfg::parse_override_file(contents, ParseMode::Both).unwrap(); + assert_eq!(override_file.toolchain.channel.as_deref(), Some("nightly")); + assert_eq!(unknown_keys, ["toolchain.channnel"]); } #[test] diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index ee69fdb81d..c30cc9a400 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -3968,6 +3968,68 @@ error: rustup could not choose a version of rustc to run, because one wasn't spe .is_ok(); } +#[tokio::test] +async fn rust_toolchain_toml_warns_on_unknown_keys() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect(["rustup", "toolchain", "install", "nightly"]) + .await + .is_ok(); + let cwd = cx.config.current_dir(); + let toolchain_file = cwd.join("rust-toolchain.toml"); + + raw::write_file( + &toolchain_file, + "[toolchain]\nchannel = \"nightly\"\nchannnel = \"stable\"", + ) + .unwrap(); + cx.config + .expect(["rustup", "show", "active-toolchain"]) + .await + .extend_redactions([("[CWD]", &cwd)]) + .with_stdout(snapbox::str![[r#" +nightly-[HOST_TUPLE] (overridden by '[CWD]/rust-toolchain.toml') + +"#]]) + .with_stderr(snapbox::str![[r#" +warn: unknown key `toolchain.channnel` in '[CWD]/rust-toolchain.toml'; it is currently ignored, but will become an error in a future release + +"#]]) + .is_ok(); + + raw::write_file( + &toolchain_file, + "[toolchain]\nchannel = \"nightly\"\n\n[future]\nkey = \"value\"", + ) + .unwrap(); + cx.config + .expect(["rustup", "show", "active-toolchain"]) + .await + .extend_redactions([("[CWD]", &cwd)]) + .with_stdout(snapbox::str![[r#" +nightly-[HOST_TUPLE] (overridden by '[CWD]/rust-toolchain.toml') + +"#]]) + .with_stderr(snapbox::str![[r#" +warn: unknown key `future` in '[CWD]/rust-toolchain.toml'; it is currently ignored, but will become an error in a future release + +"#]]) + .is_ok(); + + raw::write_file(&toolchain_file, "[toolchain]\nchannnel = \"nightly\"").unwrap(); + cx.config + .expect(["rustup", "show", "active-toolchain"]) + .await + .extend_redactions([("[CWD]", &cwd)]) + .with_stdout(snapbox::str![[""]]) + .with_stderr(snapbox::str![[r#" +warn: unknown key `toolchain.channnel` in '[CWD]/rust-toolchain.toml'; it is currently ignored, but will become an error in a future release +error: could not parse override file: '[CWD]/rust-toolchain.toml': missing toolchain properties in toolchain override file + +"#]]) + .is_err(); +} + /// Ensures that `rust-toolchain.toml` files (with `.toml` extension) only allow TOML contents #[tokio::test] async fn only_toml_in_rust_toolchain_toml() {