From e7c2fa2c400a19866ddc3eb6c62d33476239d649 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:22:41 +0200 Subject: [PATCH 01/13] Derive Default for Profile --- src/dist/dist.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/dist/dist.rs b/src/dist/dist.rs index 7a2139fa00..6d8d4e8960 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -600,9 +600,10 @@ impl TryFrom<&ToolchainName> for ToolchainDesc { } } -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum Profile { Minimal, + #[default] Default, Complete, } @@ -634,12 +635,6 @@ impl Profile { } } -impl Default for Profile { - fn default() -> Self { - Self::Default - } -} - impl fmt::Display for TargetTriple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) From 280b6626f1a9a631051e1352e462854344fa16e7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:23:04 +0200 Subject: [PATCH 02/13] Use Default impl for Settings::profile default --- src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.rs b/src/settings.rs index 43d5ec19dd..c887cf1442 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -93,7 +93,7 @@ impl Default for Settings { version: DEFAULT_METADATA_VERSION.to_owned(), default_host_triple: None, default_toolchain: None, - profile: Some(Profile::Default), + profile: Some(Profile::default()), overrides: BTreeMap::new(), pgp_keys: None, auto_self_update: None, From 5442ade0320af70e360a44a3094055a2780ec4b7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:33:46 +0200 Subject: [PATCH 03/13] Represent metadata version as an enum --- src/config.rs | 26 +++++++++++----------- src/notifications.rs | 7 +++--- src/settings.rs | 52 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3356ac28da..e91bbef497 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,7 @@ use serde::Deserialize; use thiserror::Error as ThisError; use tokio_stream::StreamExt; +use crate::settings::MetadataVersion; use crate::{ cli::self_update::SelfUpdateMode, currentprocess::process, @@ -22,7 +23,7 @@ use crate::{ fallback_settings::FallbackSettings, install::UpdateStatus, notifications::*, - settings::{Settings, SettingsFile, DEFAULT_METADATA_VERSION}, + settings::{Settings, SettingsFile}, toolchain::{ distributable::DistributableToolchain, names::{ @@ -459,20 +460,19 @@ impl Cfg { #[cfg_attr(feature = "otel", tracing::instrument(skip_all))] pub(crate) fn upgrade_data(&self) -> Result<()> { - let current_version = self.settings_file.with(|s| Ok(s.version.clone()))?; - - if current_version == DEFAULT_METADATA_VERSION { - (self.notify_handler)(Notification::MetadataUpgradeNotNeeded(¤t_version)); + let current_version = self.settings_file.with(|s| Ok(s.version))?; + if current_version == MetadataVersion::default() { + (self.notify_handler)(Notification::MetadataUpgradeNotNeeded(current_version)); return Ok(()); } (self.notify_handler)(Notification::UpgradingMetadata( - ¤t_version, - DEFAULT_METADATA_VERSION, + current_version, + MetadataVersion::default(), )); - match &*current_version { - "2" => { + match current_version { + MetadataVersion::V2 => { // The toolchain installation format changed. Just delete them all. (self.notify_handler)(Notification::UpgradeRemovesToolchains); @@ -490,11 +490,11 @@ impl Cfg { } self.settings_file.with_mut(|s| { - DEFAULT_METADATA_VERSION.clone_into(&mut s.version); + s.version = MetadataVersion::default(); Ok(()) }) } - _ => Err(RustupError::UnknownMetadataVersion(current_version).into()), + MetadataVersion::V12 => unreachable!(), } } @@ -878,8 +878,8 @@ impl Cfg { utils::assert_is_directory(&self.rustup_dir)?; self.settings_file.with(|s| { - (self.notify_handler)(Notification::ReadMetadataVersion(&s.version)); - if s.version == DEFAULT_METADATA_VERSION { + (self.notify_handler)(Notification::ReadMetadataVersion(s.version)); + if s.version == MetadataVersion::default() { Ok(()) } else { Err(anyhow!( diff --git a/src/notifications.rs b/src/notifications.rs index a43561064b..1d3fc56d84 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Display}; use std::path::{Path, PathBuf}; +use crate::settings::MetadataVersion; use crate::{ dist::{dist::ToolchainDesc, temp}, toolchain::names::ToolchainName, @@ -26,9 +27,9 @@ pub(crate) enum Notification<'a> { UninstallingToolchain(&'a ToolchainName), UninstalledToolchain(&'a ToolchainName), UpdateHashMatches, - UpgradingMetadata(&'a str, &'a str), - MetadataUpgradeNotNeeded(&'a str), - ReadMetadataVersion(&'a str), + UpgradingMetadata(MetadataVersion, MetadataVersion), + MetadataUpgradeNotNeeded(MetadataVersion), + ReadMetadataVersion(MetadataVersion), NonFatalError(&'a anyhow::Error), UpgradeRemovesToolchains, /// Both `rust-toolchain` and `rust-toolchain.toml` exist within a directory diff --git a/src/settings.rs b/src/settings.rs index c887cf1442..77d1864e53 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::collections::BTreeMap; +use std::fmt; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -12,9 +13,6 @@ use crate::notifications::*; use crate::utils::toml_utils::*; use crate::utils::utils; -pub(crate) const SUPPORTED_METADATA_VERSIONS: [&str; 2] = ["2", "12"]; -pub(crate) const DEFAULT_METADATA_VERSION: &str = "12"; - #[derive(Clone, Debug, Eq, PartialEq)] pub struct SettingsFile { path: PathBuf, @@ -78,7 +76,7 @@ impl SettingsFile { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Settings { - pub version: String, + pub version: MetadataVersion, pub default_host_triple: Option, pub default_toolchain: Option, pub profile: Option, @@ -90,7 +88,7 @@ pub struct Settings { impl Default for Settings { fn default() -> Self { Self { - version: DEFAULT_METADATA_VERSION.to_owned(), + version: MetadataVersion::default(), default_host_triple: None, default_toolchain: None, profile: Some(Profile::default()), @@ -152,9 +150,8 @@ impl Settings { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let version = get_string(&mut table, "version", path)?; - if !SUPPORTED_METADATA_VERSIONS.contains(&&*version) { - return Err(RustupError::UnknownMetadataVersion(version).into()); - } + let version = MetadataVersion::from_str(&version)?; + let auto_self_update = get_opt_string(&mut table, "auto_self_update", path)? .and_then(|mode| SelfUpdateMode::from_str(mode.as_str()).ok()); let profile = get_opt_string(&mut table, "profile", path)? @@ -172,7 +169,10 @@ impl Settings { pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); - result.insert("version".to_owned(), toml::Value::String(self.version)); + result.insert( + "version".to_owned(), + toml::Value::String(self.version.as_str().to_owned()), + ); if let Some(v) = self.default_host_triple { result.insert("default_host_triple".to_owned(), toml::Value::String(v)); @@ -227,3 +227,37 @@ impl Settings { result } } + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub(crate) enum MetadataVersion { + V2, + #[default] + V12, +} + +impl MetadataVersion { + fn as_str(&self) -> &'static str { + match self { + Self::V2 => "2", + Self::V12 => "12", + } + } +} + +impl FromStr for MetadataVersion { + type Err = RustupError; + + fn from_str(s: &str) -> Result { + match s { + "2" => Ok(Self::V2), + "12" => Ok(Self::V12), + _ => Err(RustupError::UnknownMetadataVersion(s.to_owned())), + } + } +} + +impl fmt::Display for MetadataVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} From 04005f8b06971f0212e16cc33a67b8ab33c32125 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:34:31 +0200 Subject: [PATCH 04/13] Derive Default for Settings --- src/settings.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 77d1864e53..eaf1d985ca 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -74,7 +74,7 @@ impl SettingsFile { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Settings { pub version: MetadataVersion, pub default_host_triple: Option, @@ -85,20 +85,6 @@ pub struct Settings { pub auto_self_update: Option, } -impl Default for Settings { - fn default() -> Self { - Self { - version: MetadataVersion::default(), - default_host_triple: None, - default_toolchain: None, - profile: Some(Profile::default()), - overrides: BTreeMap::new(), - pgp_keys: None, - auto_self_update: None, - } - } -} - impl Settings { fn path_to_key(path: &Path, notify_handler: &dyn Fn(Notification<'_>)) -> String { if path.exists() { From d9a97b7bef32c14eb0faea9c77158f96c50cf2fb Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:52:31 +0200 Subject: [PATCH 05/13] Add tests for settings encoding --- src/settings.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/settings.rs b/src/settings.rs index eaf1d985ca..4e9aee542e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -247,3 +247,59 @@ impl fmt::Display for MetadataVersion { write!(f, "{}", self.as_str()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_default() { + let settings = Settings::default(); + let toml = settings.stringify(); + assert_eq!( + toml, + r#"version = "12" + +[overrides] +"# + ); + } + + #[test] + fn deserialize_default() { + let toml = r#"version = "12""#; + let settings = Settings::parse(toml).unwrap(); + assert_eq!(settings.version, MetadataVersion::V12); + } + + #[test] + fn serialize_basic() { + let settings = Settings { + version: MetadataVersion::V12, + default_toolchain: Some("stable-aarch64-apple-darwin".to_owned()), + profile: Some(Profile::Default), + ..Default::default() + }; + + let toml = settings.stringify(); + assert_eq!(toml, BASIC,); + } + + #[test] + fn deserialize_basic() { + let settings = Settings::parse(BASIC).unwrap(); + assert_eq!(settings.version, MetadataVersion::V12); + assert_eq!( + settings.default_toolchain, + Some("stable-aarch64-apple-darwin".to_owned()) + ); + assert_eq!(settings.profile, Some(Profile::Default)); + } + + const BASIC: &str = r#"default_toolchain = "stable-aarch64-apple-darwin" +profile = "default" +version = "12" + +[overrides] +"#; +} From 273a878346de28e84fd83c87d72792845314555b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 10:57:51 +0200 Subject: [PATCH 06/13] Use serde to encode/decode settings --- src/cli/self_update.rs | 4 +- src/dist/dist.rs | 4 +- src/settings.rs | 118 +++++++++------------------------------- src/utils/toml_utils.rs | 16 ------ 4 files changed, 31 insertions(+), 111 deletions(-) diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index a81158ef0e..5383683cc2 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -57,6 +57,7 @@ use std::str::FromStr; use anyhow::{anyhow, Context, Result}; use cfg_if::cfg_if; use same_file::Handle; +use serde::{Deserialize, Serialize}; use crate::currentprocess::terminalsource; use crate::{ @@ -98,7 +99,8 @@ pub(crate) const NEVER_SELF_UPDATE: bool = true; #[cfg(not(feature = "no-self-update"))] pub(crate) const NEVER_SELF_UPDATE: bool = false; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] pub enum SelfUpdateMode { Enable, Disable, diff --git a/src/dist/dist.rs b/src/dist/dist.rs index 6d8d4e8960..ff1d26f469 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -10,6 +10,7 @@ use anyhow::{anyhow, bail, Context, Result}; use chrono::NaiveDate; use once_cell::sync::Lazy; use regex::Regex; +use serde::{Deserialize, Serialize}; use thiserror::Error as ThisError; pub(crate) use crate::dist::triple::*; @@ -600,7 +601,8 @@ impl TryFrom<&ToolchainName> for ToolchainDesc { } } -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] pub enum Profile { Minimal, #[default] diff --git a/src/settings.rs b/src/settings.rs index 4e9aee542e..5791e1b83a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -5,12 +5,12 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; use crate::cli::self_update::SelfUpdateMode; use crate::dist::dist::Profile; use crate::errors::*; use crate::notifications::*; -use crate::utils::toml_utils::*; use crate::utils::utils; #[derive(Clone, Debug, Eq, PartialEq)] @@ -28,8 +28,12 @@ impl SettingsFile { } fn write_settings(&self) -> Result<()> { - let s = self.cache.borrow().as_ref().unwrap().clone(); - utils::write_file("settings", &self.path, &s.stringify())?; + let settings = self.cache.borrow(); + utils::write_file( + "settings", + &self.path, + &settings.as_ref().unwrap().stringify()?, + )?; Ok(()) } @@ -74,14 +78,20 @@ impl SettingsFile { } } -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Settings { pub version: MetadataVersion, + #[serde(skip_serializing_if = "Option::is_none")] pub default_host_triple: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub default_toolchain: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub profile: Option, + #[serde(default)] pub overrides: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] pub pgp_keys: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub auto_self_update: Option, } @@ -126,97 +136,19 @@ impl Settings { } pub(crate) fn parse(data: &str) -> Result { - let value = toml::from_str(data).context("error parsing settings")?; - Self::from_toml(value, "") + toml::from_str(data).context("error parsing settings") } - pub(crate) fn stringify(self) -> String { - self.into_toml().to_string() - } - - pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - let version = get_string(&mut table, "version", path)?; - let version = MetadataVersion::from_str(&version)?; - - let auto_self_update = get_opt_string(&mut table, "auto_self_update", path)? - .and_then(|mode| SelfUpdateMode::from_str(mode.as_str()).ok()); - let profile = get_opt_string(&mut table, "profile", path)? - .and_then(|p| Profile::from_str(p.as_str()).ok()); - Ok(Self { - version, - default_host_triple: get_opt_string(&mut table, "default_host_triple", path)?, - default_toolchain: get_opt_string(&mut table, "default_toolchain", path)?, - profile, - overrides: Self::table_to_overrides(&mut table, path)?, - pgp_keys: get_opt_string(&mut table, "pgp_keys", path)?, - auto_self_update, - }) - } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - - result.insert( - "version".to_owned(), - toml::Value::String(self.version.as_str().to_owned()), - ); - - if let Some(v) = self.default_host_triple { - result.insert("default_host_triple".to_owned(), toml::Value::String(v)); - } - - if let Some(v) = self.default_toolchain { - result.insert("default_toolchain".to_owned(), toml::Value::String(v)); - } - - if let Some(v) = self.profile { - result.insert("profile".to_owned(), toml::Value::String(v.to_string())); - } - - if let Some(v) = self.pgp_keys { - result.insert("pgp_keys".to_owned(), toml::Value::String(v)); - } - - if let Some(v) = self.auto_self_update { - result.insert( - "auto_self_update".to_owned(), - toml::Value::String(v.to_string()), - ); - } - - let overrides = Self::overrides_to_table(self.overrides); - result.insert("overrides".to_owned(), toml::Value::Table(overrides)); - - result - } - - fn table_to_overrides( - table: &mut toml::value::Table, - path: &str, - ) -> Result> { - let mut result = BTreeMap::new(); - let pkg_table = get_table(table, "overrides", path)?; - - for (k, v) in pkg_table { - if let toml::Value::String(t) = v { - result.insert(k, t); - } - } - - Ok(result) - } - - fn overrides_to_table(overrides: BTreeMap) -> toml::value::Table { - let mut result = toml::value::Table::new(); - for (k, v) in overrides { - result.insert(k, toml::Value::String(v)); - } - result + fn stringify(&self) -> Result { + Ok(toml::to_string(self)?) } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) enum MetadataVersion { + #[serde(rename = "2")] V2, + #[serde(rename = "12")] #[default] V12, } @@ -255,7 +187,7 @@ mod tests { #[test] fn serialize_default() { let settings = Settings::default(); - let toml = settings.stringify(); + let toml = settings.stringify().unwrap(); assert_eq!( toml, r#"version = "12" @@ -281,8 +213,8 @@ mod tests { ..Default::default() }; - let toml = settings.stringify(); - assert_eq!(toml, BASIC,); + let toml = settings.stringify().unwrap(); + assert_eq!(toml, BASIC); } #[test] @@ -296,9 +228,9 @@ mod tests { assert_eq!(settings.profile, Some(Profile::Default)); } - const BASIC: &str = r#"default_toolchain = "stable-aarch64-apple-darwin" + const BASIC: &str = r#"version = "12" +default_toolchain = "stable-aarch64-apple-darwin" profile = "default" -version = "12" [overrides] "#; diff --git a/src/utils/toml_utils.rs b/src/utils/toml_utils.rs index 1d7376e1c6..3eb4772f0f 100644 --- a/src/utils/toml_utils.rs +++ b/src/utils/toml_utils.rs @@ -19,22 +19,6 @@ pub(crate) fn get_string(table: &mut toml::value::Table, key: &str, path: &str) } } -pub(crate) fn get_opt_string( - table: &mut toml::value::Table, - key: &str, - path: &str, -) -> Result> { - if let Ok(v) = get_value(table, key, path) { - if let toml::Value::String(s) = v { - Ok(Some(s)) - } else { - Err(ExpectedType("string", path.to_owned() + key).into()) - } - } else { - Ok(None) - } -} - pub(crate) fn get_bool(table: &mut toml::value::Table, key: &str, path: &str) -> Result { get_value(table, key, path).and_then(|v| { if let toml::Value::Boolean(b) = v { From 384ea0a8338322ee2636a25d2b84b24b19d1c8bb Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 11:05:19 +0200 Subject: [PATCH 07/13] Represent manifest version as enum --- src/dist/manifest.rs | 45 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index bc8f8479f5..5ece374de8 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -13,6 +13,7 @@ //! Docs: use std::collections::HashMap; +use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; @@ -25,8 +26,6 @@ use crate::utils::toml_utils::*; use super::{config::Config, dist::ToolchainDesc}; -pub(crate) const SUPPORTED_MANIFEST_VERSIONS: [&str; 1] = ["2"]; - /// Used by the `installed_components` function pub(crate) struct ComponentStatus { pub component: Component, @@ -37,7 +36,7 @@ pub(crate) struct ComponentStatus { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Manifest { - manifest_version: String, + manifest_version: ManifestVersion, pub date: String, pub packages: HashMap, pub renames: HashMap, @@ -133,12 +132,11 @@ impl Manifest { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let version = get_string(&mut table, "manifest-version", path)?; - if !SUPPORTED_MANIFEST_VERSIONS.contains(&&*version) { - bail!(RustupError::UnsupportedVersion(version)); - } + let manifest_version = ManifestVersion::from_str(&version)?; + let (renames, reverse_renames) = Self::table_to_renames(&mut table, path)?; Ok(Self { - manifest_version: version, + manifest_version, date: get_string(&mut table, "date", path)?, packages: Self::table_to_packages(&mut table, path)?, renames, @@ -152,7 +150,7 @@ impl Manifest { result.insert("date".to_owned(), toml::Value::String(self.date)); result.insert( "manifest-version".to_owned(), - toml::Value::String(self.manifest_version), + toml::Value::String(self.manifest_version.to_string()), ); let renames = Self::renames_to_table(self.renames); @@ -719,6 +717,37 @@ impl Component { } } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub(crate) enum ManifestVersion { + #[default] + V2, +} + +impl ManifestVersion { + pub fn as_str(&self) -> &'static str { + match self { + Self::V2 => "2", + } + } +} + +impl FromStr for ManifestVersion { + type Err = RustupError; + + fn from_str(s: &str) -> Result { + match s { + "2" => Ok(Self::V2), + _ => Err(RustupError::UnsupportedVersion(s.to_owned())), + } + } +} + +impl fmt::Display for ManifestVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + #[cfg(test)] mod tests { use crate::dist::dist::TargetTriple; From 1f1fe11bbb1a93d901ece850187a2a10bc48c0ea Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 14:52:59 +0200 Subject: [PATCH 08/13] Use serde to encode/decode manifests --- src/dist/dist.rs | 3 +- src/dist/manifest.rs | 484 +++++++++++++++----------------------- src/dist/manifestation.rs | 8 +- src/utils/toml_utils.rs | 26 -- tests/suite/cli_v2.rs | 2 +- 5 files changed, 203 insertions(+), 320 deletions(-) diff --git a/src/dist/dist.rs b/src/dist/dist.rs index ff1d26f469..ee38dbd8c4 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -140,7 +140,8 @@ pub struct ToolchainDesc { pub target: TargetTriple, } -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] +#[serde(transparent)] pub struct TargetTriple(String); // Linux hosts don't indicate clib in uname, however binaries only diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index 5ece374de8..3505bf4b34 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -12,12 +12,14 @@ //! //! Docs: +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; +use serde::{Deserialize, Serialize}; use crate::dist::dist::{Profile, TargetTriple}; use crate::errors::*; @@ -34,71 +36,200 @@ pub(crate) struct ComponentStatus { pub available: bool, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] pub struct Manifest { manifest_version: ManifestVersion, pub date: String, + #[serde(default, rename = "pkg")] pub packages: HashMap, - pub renames: HashMap, + #[serde(default)] + pub renames: HashMap, + #[serde(default, skip_serializing)] pub reverse_renames: HashMap, + #[serde(default)] profiles: HashMap>, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Renamed { + to: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Package { pub version: String, + #[serde(rename = "target")] pub targets: PackageTargets, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(from = "TargetsMap", into = "TargetsMap")] pub enum PackageTargets { Wildcard(TargetedPackage), Targeted(HashMap), } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +#[serde(transparent)] +struct TargetsMap(HashMap); + +impl From for PackageTargets { + fn from(mut map: TargetsMap) -> Self { + let wildcard = TargetTriple::new("*"); + match (map.0.len(), map.0.entry(wildcard)) { + (1, Entry::Occupied(entry)) => Self::Wildcard(entry.remove()), + (_, _) => Self::Targeted(map.0), + } + } +} + +impl From for TargetsMap { + fn from(targets: PackageTargets) -> Self { + match targets { + PackageTargets::Wildcard(tpkg) => { + let mut map = HashMap::new(); + map.insert(TargetTriple::new("*"), tpkg); + Self(map) + } + PackageTargets::Targeted(tpkgs) => Self(tpkgs), + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(from = "Target", into = "Target")] pub struct TargetedPackage { - pub bins: Vec<(CompressionKind, HashedBinary)>, + #[serde(default)] + pub bins: Vec, pub components: Vec, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CompressionKind { - GZip, - XZ, - ZStd, +#[derive(Debug, Deserialize, Serialize)] +struct Target { + available: bool, + url: Option, + hash: Option, + xz_url: Option, + xz_hash: Option, + zst_url: Option, + zst_hash: Option, + components: Option>, + extensions: Option>, } -/// Each compression kind, in order of preference for use, from most desirable -/// to least desirable. -static COMPRESSION_KIND_PREFERENCE_ORDER: &[CompressionKind] = &[ - CompressionKind::ZStd, - CompressionKind::XZ, - CompressionKind::GZip, -]; +impl From for TargetedPackage { + fn from(target: Target) -> Self { + let mut components = target.components.unwrap_or_default(); + if let Some(extensions) = target.extensions { + components.extend(extensions.into_iter().map(|mut c| { + c.is_extension = true; + c + })); + } -impl CompressionKind { - const fn key_prefix(self) -> &'static str { - match self { - Self::GZip => "", - Self::XZ => "xz_", - Self::ZStd => "zst_", + let mut bins = Vec::new(); + if !target.available { + return Self { bins, components }; + } + + if let (Some(url), Some(hash)) = (target.zst_url, target.zst_hash) { + bins.push(HashedBinary { + url, + hash, + compression: CompressionKind::ZStd, + }); + } + + if let (Some(url), Some(hash)) = (target.xz_url, target.xz_hash) { + bins.push(HashedBinary { + url, + hash, + compression: CompressionKind::XZ, + }); + } + + if let (Some(url), Some(hash)) = (target.url, target.hash) { + bins.push(HashedBinary { + url, + hash, + compression: CompressionKind::GZip, + }); + } + + Self { bins, components } + } +} + +impl From for Target { + fn from(tpkg: TargetedPackage) -> Self { + let (mut url, mut hash) = (None, None); + let (mut xz_url, mut xz_hash) = (None, None); + let (mut zst_url, mut zst_hash) = (None, None); + let available = !tpkg.bins.is_empty(); + for bin in tpkg.bins { + match bin.compression { + CompressionKind::GZip => { + url = Some(bin.url); + hash = Some(bin.hash); + } + CompressionKind::XZ => { + xz_url = Some(bin.url); + xz_hash = Some(bin.hash); + } + CompressionKind::ZStd => { + zst_url = Some(bin.url); + zst_hash = Some(bin.hash); + } + } + } + + let (mut components, mut extensions) = + (Vec::with_capacity(tpkg.components.len()), Vec::new()); + for c in tpkg.components { + match c.is_extension { + true => &mut extensions, + false => &mut components, + } + .push(c); + } + + Self { + available, + url, + hash, + xz_url, + xz_hash, + zst_url, + zst_hash, + components: Some(components), + extensions: Some(extensions), } } } +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum CompressionKind { + GZip, + XZ, + ZStd, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct HashedBinary { pub url: String, pub hash: String, + pub compression: CompressionKind, } -#[derive(Clone, Debug, Eq, Ord, PartialOrd)] +#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialOrd, Serialize)] pub struct Component { - pkg: String, + pub pkg: String, + #[serde(with = "component_target")] pub target: Option, // Older Rustup distinguished between components (which are essential) and // extensions (which are not). + #[serde(default)] is_extension: bool, } @@ -118,136 +249,43 @@ impl Hash for Component { } } -impl Manifest { - pub fn parse(data: &str) -> Result { - let value = toml::from_str(data).context("error parsing manifest")?; - let manifest = Self::from_toml(value, "")?; - manifest.validate()?; - - Ok(manifest) - } - pub fn stringify(self) -> String { - self.into_toml().to_string() - } - - pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - let version = get_string(&mut table, "manifest-version", path)?; - let manifest_version = ManifestVersion::from_str(&version)?; - - let (renames, reverse_renames) = Self::table_to_renames(&mut table, path)?; - Ok(Self { - manifest_version, - date: get_string(&mut table, "date", path)?, - packages: Self::table_to_packages(&mut table, path)?, - renames, - reverse_renames, - profiles: Self::table_to_profiles(&mut table, path)?, +mod component_target { + use super::*; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize( + target: &Option, + serializer: S, + ) -> Result { + serializer.serialize_str(match target { + Some(t) => t, + None => "*", }) } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - - result.insert("date".to_owned(), toml::Value::String(self.date)); - result.insert( - "manifest-version".to_owned(), - toml::Value::String(self.manifest_version.to_string()), - ); - - let renames = Self::renames_to_table(self.renames); - result.insert("renames".to_owned(), toml::Value::Table(renames)); - - let packages = Self::packages_to_table(self.packages); - result.insert("pkg".to_owned(), toml::Value::Table(packages)); - - let profiles = Self::profiles_to_table(self.profiles); - result.insert("profiles".to_owned(), toml::Value::Table(profiles)); - - result - } - - fn table_to_packages( - table: &mut toml::value::Table, - path: &str, - ) -> Result> { - let mut result = HashMap::new(); - let pkg_table = get_table(table, "pkg", path)?; - for (k, v) in pkg_table { - if let toml::Value::Table(t) = v { - result.insert(k, Package::from_toml(t, path)?); - } - } - - Ok(result) - } - fn packages_to_table(packages: HashMap) -> toml::value::Table { - let mut result = toml::value::Table::new(); - for (k, v) in packages { - result.insert(k, toml::Value::Table(v.into_toml())); - } - result + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + Ok(match Option::::deserialize(deserializer)? { + Some(s) if s != "*" => Some(TargetTriple::new(s)), + _ => None, + }) } +} - fn table_to_renames( - table: &mut toml::value::Table, - path: &str, - ) -> Result<(HashMap, HashMap)> { - let mut renames = HashMap::new(); - let mut reverse_renames = HashMap::new(); - let renames_table = get_table(table, "renames", path)?; - - for (k, v) in renames_table { - if let toml::Value::Table(mut t) = v { - let to = get_string(&mut t, "to", path)?; - renames.insert(k.to_owned(), to.clone()); - reverse_renames.insert(to, k.to_owned()); - } +impl Manifest { + pub fn parse(data: &str) -> Result { + let mut manifest = toml::from_str::(data).context("error parsing manifest")?; + for (from, to) in manifest.renames.iter() { + manifest.reverse_renames.insert(to.to.clone(), from.clone()); } - Ok((renames, reverse_renames)) - } - fn renames_to_table(renames: HashMap) -> toml::value::Table { - let mut result = toml::value::Table::new(); - for (from, to) in renames { - let mut table = toml::value::Table::new(); - table.insert("to".to_owned(), toml::Value::String(to)); - result.insert(from, toml::Value::Table(table)); - } - result + manifest.validate()?; + Ok(manifest) } - fn table_to_profiles( - table: &mut toml::value::Table, - path: &str, - ) -> Result>> { - let mut result = HashMap::new(); - let profile_table = match get_table(table, "profiles", path) { - Ok(t) => t, - Err(_) => return Ok(result), - }; - - for (k, v) in profile_table { - if let toml::Value::Array(a) = v { - let values = a - .into_iter() - .filter_map(|v| match v { - toml::Value::String(s) => Some(s), - _ => None, - }) - .collect(); - result.insert(Profile::from_str(&k)?, values); - } - } - - Ok(result) - } - fn profiles_to_table(profiles: HashMap>) -> toml::value::Table { - let mut result = toml::value::Table::new(); - for (profile, values) in profiles { - let array = values.into_iter().map(toml::Value::String).collect(); - result.insert(profile.to_string(), toml::Value::Array(array)); - } - result + pub fn stringify(self) -> anyhow::Result { + Ok(toml::to_string(&self)?) } pub fn get_package(&self, name: &str) -> Result<&Package> { @@ -334,7 +372,8 @@ impl Manifest { // The target of any renames must be an actual package. The subject of // renames is unconstrained. - for name in self.renames.values() { + for renamed in self.renames.values() { + let name = &renamed.to; if !self.packages.contains_key(name) { bail!(format!( "server sent a broken manifest: missing package for the target of a rename {name}" @@ -350,7 +389,7 @@ impl Manifest { pub(crate) fn rename_component(&self, component: &Component) -> Option { self.renames.get(&component.pkg).map(|r| { let mut c = component.clone(); - c.pkg.clone_from(r); + c.pkg.clone_from(&r.to); c }) } @@ -408,55 +447,6 @@ impl Manifest { } impl Package { - pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - Ok(Self { - version: get_string(&mut table, "version", path)?, - targets: Self::toml_to_targets(table, path)?, - }) - } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - - result.insert("version".to_owned(), toml::Value::String(self.version)); - - let targets = Self::targets_to_toml(self.targets); - result.insert("target".to_owned(), toml::Value::Table(targets)); - - result - } - - fn toml_to_targets(mut table: toml::value::Table, path: &str) -> Result { - let mut target_table = get_table(&mut table, "target", path)?; - - if let Some(toml::Value::Table(t)) = target_table.remove("*") { - Ok(PackageTargets::Wildcard(TargetedPackage::from_toml( - t, path, - )?)) - } else { - let mut result = HashMap::new(); - for (k, v) in target_table { - if let toml::Value::Table(t) = v { - result.insert(TargetTriple::new(k), TargetedPackage::from_toml(t, path)?); - } - } - Ok(PackageTargets::Targeted(result)) - } - } - fn targets_to_toml(targets: PackageTargets) -> toml::value::Table { - let mut result = toml::value::Table::new(); - match targets { - PackageTargets::Wildcard(tpkg) => { - result.insert("*".to_owned(), toml::Value::Table(tpkg.into_toml())); - } - PackageTargets::Targeted(tpkgs) => { - for (k, v) in tpkgs { - result.insert(k.to_string(), toml::Value::Table(v.into_toml())); - } - } - } - result - } - pub fn get_target(&self, target: Option<&TargetTriple>) -> Result<&TargetedPackage> { match self.targets { PackageTargets::Wildcard(ref tpkg) => Ok(tpkg), @@ -490,92 +480,9 @@ impl PackageTargets { } impl TargetedPackage { - pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - let components = get_array(&mut table, "components", path)?; - let extensions = get_array(&mut table, "extensions", path)?; - - let mut components = - Self::toml_to_components(components, &format!("{}{}.", path, "components"), false)?; - components.append(&mut Self::toml_to_components( - extensions, - &format!("{}{}.", path, "extensions"), - true, - )?); - - if get_bool(&mut table, "available", path)? { - let mut bins = Vec::new(); - for kind in COMPRESSION_KIND_PREFERENCE_ORDER.iter().copied() { - let url_key = format!("{}url", kind.key_prefix()); - let hash_key = format!("{}hash", kind.key_prefix()); - let url = get_string(&mut table, &url_key, path).ok(); - let hash = get_string(&mut table, &hash_key, path).ok(); - if let (Some(url), Some(hash)) = (url, hash) { - bins.push((kind, HashedBinary { url, hash })); - } - } - Ok(Self { bins, components }) - } else { - Ok(Self { - bins: Vec::new(), - components: Vec::new(), - }) - } - } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - let (components, extensions) = Self::components_to_toml(self.components); - if !components.is_empty() { - result.insert("components".to_owned(), toml::Value::Array(components)); - } - if !extensions.is_empty() { - result.insert("extensions".to_owned(), toml::Value::Array(extensions)); - } - if self.bins.is_empty() { - result.insert("available".to_owned(), toml::Value::Boolean(false)); - } else { - for (kind, bin) in self.bins { - let url_key = format!("{}url", kind.key_prefix()); - let hash_key = format!("{}hash", kind.key_prefix()); - result.insert(url_key, toml::Value::String(bin.url)); - result.insert(hash_key, toml::Value::String(bin.hash)); - } - result.insert("available".to_owned(), toml::Value::Boolean(true)); - } - result - } - pub fn available(&self) -> bool { !self.bins.is_empty() } - - fn toml_to_components( - arr: toml::value::Array, - path: &str, - is_extension: bool, - ) -> Result> { - let mut result = Vec::new(); - - for (i, v) in arr.into_iter().enumerate() { - if let toml::Value::Table(t) = v { - let path = format!("{path}[{i}]"); - result.push(Component::from_toml(t, &path, is_extension)?); - } - } - - Ok(result) - } - fn components_to_toml(data: Vec) -> (toml::value::Array, toml::value::Array) { - let mut components = toml::value::Array::new(); - let mut extensions = toml::value::Array::new(); - for v in data { - if v.is_extension { - extensions.push(toml::Value::Table(v.into_toml())); - } else { - components.push(toml::Value::Table(v.into_toml())); - } - } - (components, extensions) - } } impl Component { @@ -717,8 +624,9 @@ impl Component { } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) enum ManifestVersion { + #[serde(rename = "2")] #[default] V2, } @@ -779,8 +687,8 @@ mod tests { .get_target(Some(&x86_64_unknown_linux_gnu)) .unwrap(); assert!(rust_target_pkg.available()); - assert_eq!(rust_target_pkg.bins[0].1.url, "example.com"); - assert_eq!(rust_target_pkg.bins[0].1.hash, "..."); + assert_eq!(rust_target_pkg.bins[0].url, "example.com"); + assert_eq!(rust_target_pkg.bins[0].hash, "..."); let component = &rust_target_pkg.components[0]; assert_eq!(component.short_name_in_manifest(), "rustc"); @@ -794,14 +702,14 @@ mod tests { let docs_target_pkg = docs_pkg .get_target(Some(&x86_64_unknown_linux_gnu)) .unwrap(); - assert_eq!(docs_target_pkg.bins[0].1.url, "example.com"); + assert_eq!(docs_target_pkg.bins[0].url, "example.com"); } #[test] fn renames() { let manifest = Manifest::parse(EXAMPLE2).unwrap(); assert_eq!(1, manifest.renames.len()); - assert_eq!(manifest.renames["cargo-old"], "cargo"); + assert_eq!(manifest.renames["cargo-old"].to, "cargo"); assert_eq!(1, manifest.reverse_renames.len()); assert_eq!(manifest.reverse_renames["cargo"], "cargo-old"); } @@ -809,12 +717,12 @@ mod tests { #[test] fn parse_round_trip() { let original = Manifest::parse(EXAMPLE).unwrap(); - let serialized = original.clone().stringify(); + let serialized = original.clone().stringify().unwrap(); let new = Manifest::parse(&serialized).unwrap(); assert_eq!(original, new); let original = Manifest::parse(EXAMPLE2).unwrap(); - let serialized = original.clone().stringify(); + let serialized = original.clone().stringify().unwrap(); let new = Manifest::parse(&serialized).unwrap(); assert_eq!(original, new); } diff --git a/src/dist/manifestation.rs b/src/dist/manifestation.rs index d12c7a0b68..462b0346ad 100644 --- a/src/dist/manifestation.rs +++ b/src/dist/manifestation.rs @@ -275,7 +275,7 @@ impl Manifestation { } // Install new distribution manifest - let new_manifest_str = new_manifest.clone().stringify(); + let new_manifest_str = new_manifest.clone().stringify()?; tx.modify_file(rel_installed_manifest_path)?; utils::write_file("manifest", &installed_manifest_path, &new_manifest_str)?; @@ -709,9 +709,9 @@ impl Update { // manifest leaves us with the files/hash pairs in preference order. components_urls_and_hashes.push(( component.clone(), - target_package.bins[0].0, - target_package.bins[0].1.url.clone(), - target_package.bins[0].1.hash.clone(), + target_package.bins[0].compression, + target_package.bins[0].url.clone(), + target_package.bins[0].hash.clone(), )); } diff --git a/src/utils/toml_utils.rs b/src/utils/toml_utils.rs index 3eb4772f0f..5c97b47092 100644 --- a/src/utils/toml_utils.rs +++ b/src/utils/toml_utils.rs @@ -19,32 +19,6 @@ pub(crate) fn get_string(table: &mut toml::value::Table, key: &str, path: &str) } } -pub(crate) fn get_bool(table: &mut toml::value::Table, key: &str, path: &str) -> Result { - get_value(table, key, path).and_then(|v| { - if let toml::Value::Boolean(b) = v { - Ok(b) - } else { - Err(ExpectedType("bool", path.to_owned() + key).into()) - } - }) -} - -pub(crate) fn get_table( - table: &mut toml::value::Table, - key: &str, - path: &str, -) -> Result { - if let Some(v) = table.remove(key) { - if let toml::Value::Table(t) = v { - Ok(t) - } else { - Err(ExpectedType("table", path.to_owned() + key).into()) - } - } else { - Ok(toml::value::Table::new()) - } -} - pub(crate) fn get_array( table: &mut toml::value::Table, key: &str, diff --git a/tests/suite/cli_v2.rs b/tests/suite/cli_v2.rs index 77bc623a5a..a0c6acdaa7 100644 --- a/tests/suite/cli_v2.rs +++ b/tests/suite/cli_v2.rs @@ -1076,7 +1076,7 @@ fn make_component_unavailable(config: &Config, name: &str, target: String) { let target_pkg = std_pkg.targets.get_mut(&target).unwrap(); target_pkg.bins = Vec::new(); } - let manifest_str = manifest.stringify(); + let manifest_str = manifest.stringify().unwrap(); rustup::utils::raw::write_file(&manifest_path, &manifest_str).unwrap(); // Have to update the hash too From 0918cf7d901457f24bcde3b73c6ea2143a835ddd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 15:09:42 +0200 Subject: [PATCH 09/13] Represent config version as an enum --- src/dist/config.rs | 50 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/dist/config.rs b/src/dist/config.rs index 492426493c..498f769f6f 100644 --- a/src/dist/config.rs +++ b/src/dist/config.rs @@ -1,24 +1,22 @@ -use anyhow::{bail, Context, Result}; +use std::fmt; +use std::str::FromStr; + +use anyhow::{Context, Result}; use super::manifest::Component; use crate::errors::*; use crate::utils::toml_utils::*; -pub(crate) const SUPPORTED_CONFIG_VERSIONS: [&str; 1] = ["1"]; -pub(crate) const DEFAULT_CONFIG_VERSION: &str = "1"; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Config { - pub config_version: String, + pub config_version: ConfigVersion, pub components: Vec, } impl Config { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let config_version = get_string(&mut table, "config_version", path)?; - if !SUPPORTED_CONFIG_VERSIONS.contains(&&*config_version) { - bail!(RustupError::UnsupportedVersion(config_version)); - } + let config_version = ConfigVersion::from_str(&config_version)?; let components = get_array(&mut table, "components", path)?; let components = @@ -33,7 +31,7 @@ impl Config { let mut result = toml::value::Table::new(); result.insert( "config_version".to_owned(), - toml::Value::String(self.config_version), + toml::Value::String(self.config_version.as_str().to_owned()), ); let components = Self::components_to_toml(self.components); if !components.is_empty() { @@ -77,11 +75,33 @@ impl Config { } } -impl Default for Config { - fn default() -> Self { - Self { - config_version: DEFAULT_CONFIG_VERSION.to_owned(), - components: Vec::new(), +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub(crate) enum ConfigVersion { + #[default] + V1, +} + +impl ConfigVersion { + pub fn as_str(&self) -> &'static str { + match self { + Self::V1 => "1", } } } + +impl FromStr for ConfigVersion { + type Err = RustupError; + + fn from_str(s: &str) -> Result { + match s { + "1" => Ok(Self::V1), + _ => Err(RustupError::UnsupportedVersion(s.to_owned())), + } + } +} + +impl fmt::Display for ConfigVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} From 2070bb7a42ac0635133f4d6855aecd8acebae54a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 15:12:52 +0200 Subject: [PATCH 10/13] Use serde to encode/decode config --- src/dist/config.rs | 61 +++++---------------------------------- src/dist/manifest.rs | 35 ++-------------------- src/dist/manifestation.rs | 2 +- src/utils/mod.rs | 1 - src/utils/toml_utils.rs | 36 ----------------------- 5 files changed, 10 insertions(+), 125 deletions(-) delete mode 100644 src/utils/toml_utils.rs diff --git a/src/dist/config.rs b/src/dist/config.rs index 498f769f6f..af1216e4a6 100644 --- a/src/dist/config.rs +++ b/src/dist/config.rs @@ -2,72 +2,24 @@ use std::fmt; use std::str::FromStr; use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; use super::manifest::Component; use crate::errors::*; -use crate::utils::toml_utils::*; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Config { pub config_version: ConfigVersion, pub components: Vec, } impl Config { - pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - let config_version = get_string(&mut table, "config_version", path)?; - let config_version = ConfigVersion::from_str(&config_version)?; - - let components = get_array(&mut table, "components", path)?; - let components = - Self::toml_to_components(components, &format!("{}{}.", path, "components"))?; - - Ok(Self { - config_version, - components, - }) - } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - result.insert( - "config_version".to_owned(), - toml::Value::String(self.config_version.as_str().to_owned()), - ); - let components = Self::components_to_toml(self.components); - if !components.is_empty() { - result.insert("components".to_owned(), toml::Value::Array(components)); - } - result - } - pub(crate) fn parse(data: &str) -> Result { - let value = toml::from_str(data).context("error parsing config")?; - Self::from_toml(value, "") - } - - pub(crate) fn stringify(self) -> String { - self.into_toml().to_string() + toml::from_str(data).context("error parsing config") } - fn toml_to_components(arr: toml::value::Array, path: &str) -> Result> { - let mut result = Vec::new(); - - for (i, v) in arr.into_iter().enumerate() { - if let toml::Value::Table(t) = v { - let path = format!("{path}[{i}]"); - result.push(Component::from_toml(t, &path, false)?); - } - } - - Ok(result) - } - - fn components_to_toml(components: Vec) -> toml::value::Array { - let mut result = toml::value::Array::new(); - for v in components { - result.push(toml::Value::Table(v.into_toml())); - } - result + pub(crate) fn stringify(&self) -> Result { + Ok(toml::to_string(&self)?) } pub(crate) fn new() -> Self { @@ -75,8 +27,9 @@ impl Config { } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) enum ConfigVersion { + #[serde(rename = "1")] #[default] V1, } diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index 3505bf4b34..381dda4c67 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -21,12 +21,10 @@ use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; use serde::{Deserialize, Serialize}; +use super::{config::Config, dist::ToolchainDesc}; use crate::dist::dist::{Profile, TargetTriple}; use crate::errors::*; use crate::toolchain::distributable::DistributableToolchain; -use crate::utils::toml_utils::*; - -use super::{config::Config, dist::ToolchainDesc}; /// Used by the `installed_components` function pub(crate) struct ComponentStatus { @@ -529,36 +527,7 @@ impl Component { is_extension: false, } } - pub(crate) fn from_toml( - mut table: toml::value::Table, - path: &str, - is_extension: bool, - ) -> Result { - Ok(Self { - pkg: get_string(&mut table, "pkg", path)?, - target: get_string(&mut table, "target", path).map(|s| { - if s == "*" { - None - } else { - Some(TargetTriple::new(s)) - } - })?, - is_extension, - }) - } - pub(crate) fn into_toml(self) -> toml::value::Table { - let mut result = toml::value::Table::new(); - result.insert( - "target".to_owned(), - toml::Value::String( - self.target - .map(|t| t.to_string()) - .unwrap_or_else(|| "*".to_owned()), - ), - ); - result.insert("pkg".to_owned(), toml::Value::String(self.pkg)); - result - } + pub(crate) fn name(&self, manifest: &Manifest) -> String { let pkg = self.short_name(manifest); if let Some(ref t) = self.target { diff --git a/src/dist/manifestation.rs b/src/dist/manifestation.rs index 462b0346ad..60468f53a4 100644 --- a/src/dist/manifestation.rs +++ b/src/dist/manifestation.rs @@ -287,7 +287,7 @@ impl Manifestation { // name/target. Needs to be fixed in rust-installer. let mut new_config = Config::new(); new_config.components = update.final_component_list; - let config_str = new_config.stringify(); + let config_str = new_config.stringify()?; let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_path = prefix.path().join(&rel_config_path); tx.modify_file(rel_config_path)?; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7aaea2544a..a002fe27c9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,6 @@ //! Utility functions for Rustup pub(crate) mod notifications; pub mod raw; -pub(crate) mod toml_utils; pub(crate) mod units; #[allow(clippy::module_inception)] pub mod utils; diff --git a/src/utils/toml_utils.rs b/src/utils/toml_utils.rs deleted file mode 100644 index 5c97b47092..0000000000 --- a/src/utils/toml_utils.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anyhow::{anyhow, Result}; -use thiserror::Error as ThisError; - -fn get_value(table: &mut toml::value::Table, key: &str, path: &str) -> Result { - table - .remove(key) - .ok_or_else(|| anyhow!(format!("missing key: '{}'", path.to_owned() + key))) -} - -#[derive(Debug, ThisError)] -#[error("expected type: '{0}' for '{1}'")] -struct ExpectedType(&'static str, String); - -pub(crate) fn get_string(table: &mut toml::value::Table, key: &str, path: &str) -> Result { - if let toml::Value::String(s) = get_value(table, key, path)? { - Ok(s) - } else { - Err(ExpectedType("string", path.to_owned() + key).into()) - } -} - -pub(crate) fn get_array( - table: &mut toml::value::Table, - key: &str, - path: &str, -) -> Result { - if let Some(v) = table.remove(key) { - if let toml::Value::Array(s) = v { - Ok(s) - } else { - Err(ExpectedType("array", path.to_owned() + key).into()) - } - } else { - Ok(toml::value::Array::new()) - } -} From ea5c7573ae1c44aeee4b004f26295049b9018ab7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 15:19:39 +0200 Subject: [PATCH 11/13] Use serde to encode/decode rustup manifests --- src/cli/self_update.rs | 63 ++++++++++++++++++++++++------------- tests/suite/cli_self_upd.rs | 2 +- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 5383683cc2..14c1827b2e 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -46,13 +46,13 @@ mod os { } use std::borrow::Cow; -use std::env; use std::env::consts::EXE_SUFFIX; use std::fs; use std::io::Write; use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; use std::process::Command; use std::str::FromStr; +use std::{env, fmt}; use anyhow::{anyhow, Context, Result}; use cfg_if::cfg_if; @@ -60,6 +60,7 @@ use same_file::Handle; use serde::{Deserialize, Serialize}; use crate::currentprocess::terminalsource; +use crate::errors::RustupError; use crate::{ cli::{ common::{self, ignorable_error, report_error, Confirm, PackageUpdate}, @@ -1247,29 +1248,49 @@ async fn get_available_rustup_version() -> Result { let release_file = tempdir.path().join("release-stable.toml"); utils::download_file(&release_file_url, &release_file, None, &|_| ()).await?; let release_toml_str = utils::read_file("rustup release", &release_file)?; - let release_toml: toml::Value = - toml::from_str(&release_toml_str).context("unable to parse rustup release file")?; - - // Check the release file schema. - let schema = release_toml - .get("schema-version") - .ok_or_else(|| anyhow!("no schema key in rustup release file"))? - .as_str() - .ok_or_else(|| anyhow!("invalid schema key in rustup release file"))?; - if schema != "1" { - return Err(anyhow!(format!( - "unknown schema version '{schema}' in rustup release file" - ))); + let release_toml = toml::from_str::(&release_toml_str) + .context("unable to parse rustup release file")?; + + Ok(release_toml.version) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +struct RustupManifest { + schema_version: SchemaVersion, + version: String, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub(crate) enum SchemaVersion { + #[serde(rename = "1")] + #[default] + V1, +} + +impl SchemaVersion { + pub fn as_str(&self) -> &'static str { + match self { + Self::V1 => "1", + } } +} - // Get the version. - let available_version = release_toml - .get("version") - .ok_or_else(|| anyhow!("no version key in rustup release file"))? - .as_str() - .ok_or_else(|| anyhow!("invalid version key in rustup release file"))?; +impl FromStr for SchemaVersion { + type Err = RustupError; - Ok(String::from(available_version)) + fn from_str(s: &str) -> Result { + match s { + "1" => Ok(Self::V1), + _ => Err(RustupError::UnsupportedVersion(s.to_owned())), + } + } +} + +impl fmt::Display for SchemaVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } } pub(crate) async fn check_rustup_update() -> Result<()> { diff --git a/tests/suite/cli_self_upd.rs b/tests/suite/cli_self_upd.rs index 45d6b93481..ed875dff75 100644 --- a/tests/suite/cli_self_upd.rs +++ b/tests/suite/cli_self_upd.rs @@ -437,7 +437,7 @@ fn update_bad_schema() { update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); output_release_file(self_dist, "17", "1.1.1"); - config.expect_err(&["rustup", "self", "update"], "unknown schema version"); + config.expect_err(&["rustup", "self", "update"], "unknown variant"); }); } From 53e946b77f260217be165379455093f4f92b4091 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 6 Jun 2024 15:53:57 +0200 Subject: [PATCH 12/13] Use serde to encode/decode mock manifests --- src/dist/manifest.rs | 8 +- src/dist/manifestation/tests.rs | 4 +- src/test/mock/clitools.rs | 8 +- src/test/mock/dist.rs | 168 +++++++++++++++----------------- 4 files changed, 88 insertions(+), 100 deletions(-) diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index 381dda4c67..2ff4117322 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -37,7 +37,7 @@ pub(crate) struct ComponentStatus { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] pub struct Manifest { - manifest_version: ManifestVersion, + pub(crate) manifest_version: ManifestVersion, pub date: String, #[serde(default, rename = "pkg")] pub packages: HashMap, @@ -46,12 +46,12 @@ pub struct Manifest { #[serde(default, skip_serializing)] pub reverse_renames: HashMap, #[serde(default)] - profiles: HashMap>, + pub profiles: HashMap>, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Renamed { - to: String, + pub to: String, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -228,7 +228,7 @@ pub struct Component { // Older Rustup distinguished between components (which are essential) and // extensions (which are not). #[serde(default)] - is_extension: bool, + pub is_extension: bool, } impl PartialEq for Component { diff --git a/src/dist/manifestation/tests.rs b/src/dist/manifestation/tests.rs index d7f66d1a65..4e6b6ce5f9 100644 --- a/src/dist/manifestation/tests.rs +++ b/src/dist/manifestation/tests.rs @@ -250,7 +250,7 @@ fn mock_dist_server_smoke_test() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let path = tempdir.path(); - create_mock_dist_server(path, None).write(&[ManifestVersion::V2], false, false); + create_mock_dist_server(path, None).write(&[MockManifestVersion::V2], false, false); assert!(utils::path_exists(path.join( "dist/2016-02-01/rustc-nightly-x86_64-apple-darwin.tar.gz" @@ -552,7 +552,7 @@ fn setup_from_dist_server( f: &dyn Fn(&Url, &ToolchainDesc, &InstallPrefix, &DownloadCfg<'_>, &temp::Context), ) { server.write( - &[ManifestVersion::V2], + &[MockManifestVersion::V2], comps.enable_xz(), comps.enable_zst(), ); diff --git a/src/test/mock/clitools.rs b/src/test/mock/clitools.rs index efd093ffef..731e0c97e4 100644 --- a/src/test/mock/clitools.rs +++ b/src/test/mock/clitools.rs @@ -28,7 +28,7 @@ use crate::utils::{raw, utils}; use super::{ dist::{ - change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, + change_channel_date, MockChannel, MockComponent, MockDistServer, MockManifestVersion, MockPackage, MockTargetedPackage, }, topical_doc_data, MockComponentBuilder, MockFile, MockInstallerBuilder, @@ -1134,8 +1134,8 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { let vs = match s { Scenario::None => unreachable!("None exits above"), Scenario::Empty => vec![], - Scenario::Full => vec![ManifestVersion::V1, ManifestVersion::V2], - Scenario::SimpleV1 | Scenario::ArchivesV1 => vec![ManifestVersion::V1], + Scenario::Full => vec![MockManifestVersion::V1, MockManifestVersion::V2], + Scenario::SimpleV1 | Scenario::ArchivesV1 => vec![MockManifestVersion::V1], Scenario::SimpleV2 | Scenario::ArchivesV2 | Scenario::ArchivesV2_2015_01_01 @@ -1148,7 +1148,7 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { | Scenario::HostGoesMissingBefore | Scenario::HostGoesMissingAfter | Scenario::MissingComponent - | Scenario::MissingComponentMulti => vec![ManifestVersion::V2], + | Scenario::MissingComponentMulti => vec![MockManifestVersion::V2], }; MockDistServer { diff --git a/src/test/mock/dist.rs b/src/test/mock/dist.rs index 9d0c138644..bc606411c9 100644 --- a/src/test/mock/dist.rs +++ b/src/test/mock/dist.rs @@ -11,6 +11,12 @@ use once_cell::sync::Lazy; use sha2::{Digest, Sha256}; use url::Url; +use crate::dist::dist::{Profile, TargetTriple}; +use crate::dist::manifest::{ + Component, CompressionKind, HashedBinary, Manifest, ManifestVersion, Package, PackageTargets, + Renamed, TargetedPackage, +}; + use super::clitools::hard_link; use super::MockInstallerBuilder; @@ -118,14 +124,14 @@ pub struct MockHashes { pub zst: Option, } -pub enum ManifestVersion { +pub enum MockManifestVersion { V1, V2, } impl MockDistServer { #[cfg_attr(feature = "otel", tracing::instrument(skip_all))] - pub fn write(&self, vs: &[ManifestVersion], enable_xz: bool, enable_zst: bool) { + pub fn write(&self, vs: &[MockManifestVersion], enable_xz: bool, enable_zst: bool) { fs::create_dir_all(&self.path).unwrap(); for channel in self.channels.iter() { @@ -136,8 +142,8 @@ impl MockDistServer { } for v in vs { match *v { - ManifestVersion::V1 => self.write_manifest_v1(channel), - ManifestVersion::V2 => self.write_manifest_v2(channel, &hashes), + MockManifestVersion::V1 => self.write_manifest_v1(channel), + MockManifestVersion::V2 => self.write_manifest_v2(channel, &hashes), } } } @@ -306,136 +312,118 @@ impl MockDistServer { channel: &MockChannel, hashes: &HashMap, ) { - let mut toml_manifest = toml::value::Table::new(); - - toml_manifest.insert( - String::from("manifest-version"), - toml::Value::String(MOCK_MANIFEST_VERSION.to_owned()), - ); - toml_manifest.insert( - String::from("date"), - toml::Value::String(channel.date.to_owned()), - ); + let mut manifest = Manifest { + manifest_version: ManifestVersion::V2, + date: channel.date.clone(), + renames: HashMap::default(), + packages: HashMap::default(), + reverse_renames: HashMap::default(), + profiles: HashMap::default(), + }; // [pkg.*] - let mut toml_packages = toml::value::Table::new(); for package in &channel.packages { - let mut toml_package = toml::value::Table::new(); - toml_package.insert( - String::from("version"), - toml::Value::String(package.version.to_owned()), - ); + let mut targets = HashMap::default(); // [pkg.*.target.*] - let mut toml_targets = toml::value::Table::new(); for target in &package.targets { - let mut toml_target = toml::value::Table::new(); - toml_target.insert( - String::from("available"), - toml::Value::Boolean(target.available), - ); + let mut tpkg = TargetedPackage { + bins: Vec::new(), + components: Vec::new(), + }; let package_file_name = if target.target != "*" { format!("{}-{}-{}.tar.gz", package.name, channel.name, target.target) } else { format!("{}-{}.tar.gz", package.name, channel.name) }; + let path = self .path .join("dist") .join(&channel.date) .join(package_file_name); - let url = format!("file://{}", path.to_string_lossy()); - toml_target.insert(String::from("url"), toml::Value::String(url.clone())); let component = MockComponent { name: package.name.to_owned(), target: target.target.to_owned(), is_extension: false, }; - let hash = hashes[&component].clone(); - toml_target.insert(String::from("hash"), toml::Value::String(hash.gz)); - - if let Some(xz_hash) = hash.xz { - toml_target.insert( - String::from("xz_url"), - toml::Value::String(url.replace(".tar.gz", ".tar.xz")), - ); - toml_target.insert(String::from("xz_hash"), toml::Value::String(xz_hash)); - } - if let Some(zst_hash) = hash.zst { - toml_target.insert( - String::from("zst_url"), - toml::Value::String(url.replace(".tar.gz", ".tar.zst")), - ); - toml_target.insert(String::from("zst_hash"), toml::Value::String(zst_hash)); + + if target.available { + let hash = hashes[&component].clone(); + let url = format!("file://{}", path.to_string_lossy()); + tpkg.bins.push(HashedBinary { + url: url.clone(), + hash: hash.gz, + compression: CompressionKind::GZip, + }); + + if let Some(xz_hash) = hash.xz { + tpkg.bins.push(HashedBinary { + url: url.replace(".tar.gz", ".tar.xz"), + hash: xz_hash, + compression: CompressionKind::XZ, + }); + } + + if let Some(zst_hash) = hash.zst { + tpkg.bins.push(HashedBinary { + url: url.replace(".tar.gz", ".tar.zst"), + hash: zst_hash, + compression: CompressionKind::ZStd, + }); + } } // [pkg.*.target.*.components.*] and [pkg.*.target.*.extensions.*] - let mut toml_components = toml::value::Array::new(); - let mut toml_extensions = toml::value::Array::new(); for component in &target.components { - let mut toml_component = toml::value::Table::new(); - toml_component.insert( - String::from("pkg"), - toml::Value::String(component.name.to_owned()), - ); - toml_component.insert( - String::from("target"), - toml::Value::String(component.target.to_owned()), - ); - if component.is_extension { - toml_extensions.push(toml::Value::Table(toml_component)); - } else { - toml_components.push(toml::Value::Table(toml_component)); - } + tpkg.components.push(Component { + pkg: component.name.to_owned(), + target: Some(TargetTriple::new(&component.target)), + is_extension: component.is_extension, + }); } - toml_target.insert( - String::from("components"), - toml::Value::Array(toml_components), - ); - toml_target.insert( - String::from("extensions"), - toml::Value::Array(toml_extensions), - ); - - toml_targets.insert(target.target.clone(), toml::Value::Table(toml_target)); + + targets.insert(TargetTriple::new(&target.target), tpkg); } - toml_package.insert(String::from("target"), toml::Value::Table(toml_targets)); - toml_packages.insert(String::from(package.name), toml::Value::Table(toml_package)); + manifest.packages.insert( + package.name.to_owned(), + Package { + version: package.version.clone(), + targets: PackageTargets::Targeted(targets), + }, + ); } - toml_manifest.insert(String::from("pkg"), toml::Value::Table(toml_packages)); - let mut toml_renames = toml::value::Table::new(); for (from, to) in &channel.renames { - let mut toml_rename = toml::value::Table::new(); - toml_rename.insert(String::from("to"), toml::Value::String(to.to_owned())); - toml_renames.insert(from.to_owned(), toml::Value::Table(toml_rename)); + manifest + .renames + .insert(from.to_owned(), Renamed { to: to.to_owned() }); } - toml_manifest.insert(String::from("renames"), toml::Value::Table(toml_renames)); - let mut toml_profiles = toml::value::Table::new(); let profiles = &[ - ("minimal", vec!["rustc"]), - ("default", vec!["rustc", "cargo", "rust-std", "rust-docs"]), + (Profile::Minimal, &["rustc"][..]), ( - "complete", - vec!["rustc", "cargo", "rust-std", "rust-docs", "rls"], + Profile::Default, + &["rustc", "cargo", "rust-std", "rust-docs"], + ), + ( + Profile::Complete, + &["rustc", "cargo", "rust-std", "rust-docs", "rls"], ), ]; + for (profile, values) in profiles { - let array = values - .iter() - .map(|v| toml::Value::String((**v).to_owned())) - .collect(); - toml_profiles.insert((*profile).to_string(), toml::Value::Array(array)); + manifest + .profiles + .insert(*profile, values.iter().map(|&v| v.to_owned()).collect()); } - toml_manifest.insert(String::from("profiles"), toml::Value::Table(toml_profiles)); let manifest_name = format!("dist/channel-rust-{}", channel.name); let manifest_path = self.path.join(format!("{manifest_name}.toml")); - let manifest_content = toml::to_string(&toml_manifest).unwrap(); + let manifest_content = manifest.stringify().unwrap(); write_file(&manifest_path, &manifest_content); let hash_path = self.path.join(format!("{manifest_name}.toml.sha256")); From 603ad72eebb4208e86d1612f1f140393d69b2462 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 8 Jun 2024 15:03:26 +0200 Subject: [PATCH 13/13] Remove trivial new() implementation --- src/dist/config.rs | 4 ---- src/dist/manifestation.rs | 6 ++++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/dist/config.rs b/src/dist/config.rs index af1216e4a6..6dc4fa5bee 100644 --- a/src/dist/config.rs +++ b/src/dist/config.rs @@ -21,10 +21,6 @@ impl Config { pub(crate) fn stringify(&self) -> Result { Ok(toml::to_string(&self)?) } - - pub(crate) fn new() -> Self { - Default::default() - } } #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/src/dist/manifestation.rs b/src/dist/manifestation.rs index 60468f53a4..6abf92eb82 100644 --- a/src/dist/manifestation.rs +++ b/src/dist/manifestation.rs @@ -285,8 +285,10 @@ impl Manifestation { // that identify installed components. The rust-installer metadata maintained by // `Components` *also* tracks what is installed, but it only tracks names, not // name/target. Needs to be fixed in rust-installer. - let mut new_config = Config::new(); - new_config.components = update.final_component_list; + let new_config = Config { + components: update.final_component_list, + ..Config::default() + }; let config_str = new_config.stringify()?; let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_path = prefix.path().join(&rel_config_path);