From b181569a90f6f979121056fc9135284f82e3e70a Mon Sep 17 00:00:00 2001 From: Davide Rizzo Date: Thu, 31 Aug 2023 14:57:35 +0200 Subject: [PATCH] missing workspace_default_members should panic --- src/lib.rs | 60 +++++++++++++++++++++++++++++++++--------- tests/selftest.rs | 20 +++++++++++--- tests/test_samples.rs | 61 +++++++++++++++++++++++++++++++------------ 3 files changed, 109 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a05c0a6a..d7c3413c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,10 +151,11 @@ pub struct Metadata { pub packages: Vec, /// A list of all workspace members pub workspace_members: Vec, - /// A list of all workspace members + /// The list of default workspace members /// - /// This is always `None` if running with a version of Cargo older than 1.71. - pub workspace_default_members: Option>, + /// This not available if running with a version of Cargo older than 1.71. + #[serde(skip_serializing_if = "workspace_default_members_is_missing")] + pub workspace_default_members: WorkspaceDefaultMembers, /// Dependencies graph pub resolve: Option, /// Workspace root @@ -197,16 +198,14 @@ impl Metadata { /// Get the workspace default packages. /// - /// This will always return `None` if running with a version of Cargo older than 1.71. - pub fn workspace_default_packages(&self) -> Option> { - self.workspace_default_members - .as_ref() - .map(|workspace_default_members| { - self.packages - .iter() - .filter(|&p| workspace_default_members.contains(&p.id)) - .collect() - }) + /// # Panics + /// + /// This will panic if running with a version of Cargo older than 1.71. + pub fn workspace_default_packages(&self) -> Vec<&Package> { + self.packages + .iter() + .filter(|&p| self.workspace_default_members.contains(&p.id)) + .collect() } } @@ -221,6 +220,41 @@ impl<'a> std::ops::Index<&'a PackageId> for Metadata { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(transparent)] +/// A list of default workspace members. +/// +/// See [`Metadata::workspace_default_members`]. +/// +/// It is only available if running a version of Cargo of 1.71 or newer. +/// +/// # Panics +/// +/// Dereferencing when running an older version of Cargo will panic. +pub struct WorkspaceDefaultMembers(Option>); + +impl core::ops::Deref for WorkspaceDefaultMembers { + type Target = [PackageId]; + + fn deref(&self) -> &Self::Target { + self.0 + .as_ref() + .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71") + } +} + +/// Return true if a valid value for [`WorkspaceDefaultMembers`] is missing, and +/// dereferencing it would panic. +/// +/// Internal helper for `skip_serializing_if` and test code. Might be removed in +/// the future. +#[doc(hidden)] +pub fn workspace_default_members_is_missing( + workspace_default_members: &WorkspaceDefaultMembers, +) -> bool { + workspace_default_members.0.is_none() +} + #[derive(Clone, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "builder", derive(Builder))] #[non_exhaustive] diff --git a/tests/selftest.rs b/tests/selftest.rs index ab80e437..dbcc67ec 100644 --- a/tests/selftest.rs +++ b/tests/selftest.rs @@ -135,9 +135,6 @@ fn metadata_deps() { let workspace_packages = metadata.workspace_packages(); assert_eq!(workspace_packages.len(), 1); assert_eq!(&workspace_packages[0].id, this_id); - if let Some(default_packages) = metadata.workspace_default_packages() { - assert_eq!(default_packages, workspace_packages); - } let lib = this .targets @@ -164,3 +161,20 @@ fn metadata_deps() { assert!(serde.req.matches(&Version::parse("1.99.99").unwrap())); assert!(!serde.req.matches(&Version::parse("2.0.0").unwrap())); } + +#[test] +fn workspace_default_packages() { + use cargo_metadata::workspace_default_members_is_missing; + + let metadata = MetadataCommand::new() + .manifest_path("Cargo.toml") + .exec() + .unwrap(); + let workspace_packages = metadata.workspace_packages(); + // this will only trigger on cargo versions that expose + // workspace_default_members (that is, cargo >= 1.71) + if !workspace_default_members_is_missing(&metadata.workspace_default_members) { + let default_packages = metadata.workspace_default_packages(); + assert_eq!(default_packages, workspace_packages); + } +} diff --git a/tests/test_samples.rs b/tests/test_samples.rs index 1f45d548..c834778a 100644 --- a/tests/test_samples.rs +++ b/tests/test_samples.rs @@ -5,19 +5,19 @@ extern crate serde_json; use camino::Utf8PathBuf; use cargo_metadata::{ - ArtifactDebuginfo, CargoOpt, DependencyKind, Edition, Message, Metadata, MetadataCommand, + workspace_default_members_is_missing, ArtifactDebuginfo, CargoOpt, DependencyKind, Edition, + Message, Metadata, MetadataCommand, }; -#[test] -fn old_minimal() { - // Output from oldest supported version (1.24). - // This intentionally has as many null fields as possible. - // 1.8 is when metadata was introduced. - // Older versions not supported because the following are required: - // - `workspace_members` added in 1.13 - // - `target_directory` added in 1.19 - // - `workspace_root` added in 1.24 - let json = r#" +/// Output from oldest version ever supported (1.24). +/// +/// This intentionally has as many null fields as possible. +/// 1.8 is when metadata was introduced. +/// Older versions not supported because the following are required: +/// - `workspace_members` added in 1.13 +/// - `target_directory` added in 1.19 +/// - `workspace_root` added in 1.24 +const JSON_OLD_MINIMAL: &str = r#" { "packages": [ { @@ -65,7 +65,10 @@ fn old_minimal() { "workspace_root": "/foo" } "#; - let meta: Metadata = serde_json::from_str(json).unwrap(); + +#[test] +fn old_minimal() { + let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).unwrap(); assert_eq!(meta.packages.len(), 1); let pkg = &meta.packages[0]; assert_eq!(pkg.name, "foo"); @@ -121,6 +124,15 @@ fn old_minimal() { assert_eq!(meta.workspace_root, "/foo"); assert_eq!(meta.workspace_metadata, serde_json::Value::Null); assert_eq!(meta.target_directory, "/foo/target"); + + assert!(workspace_default_members_is_missing( + &meta.workspace_default_members + )); + let serialized = serde_json::to_value(meta).unwrap(); + assert!(!serialized + .as_object() + .unwrap() + .contains_key("workspace_default_members")); } macro_rules! sorted { @@ -188,7 +200,7 @@ fn all_the_fields() { .unwrap(); assert_eq!(meta.workspace_root.file_name().unwrap(), "all"); assert_eq!( - serde_json::from_value::(meta.workspace_metadata).unwrap(), + serde_json::from_value::(meta.workspace_metadata.clone()).unwrap(), WorkspaceMetadata { testobject: TestObject { myvalue: "abc".to_string() @@ -198,9 +210,7 @@ fn all_the_fields() { assert_eq!(meta.workspace_members.len(), 1); assert!(meta.workspace_members[0].to_string().starts_with("all")); if ver >= semver::Version::parse("1.71.0").unwrap() { - assert_eq!(meta.workspace_default_members, Some(meta.workspace_members)); - } else { - assert_eq!(meta.workspace_default_members, None); + assert_eq!(&*meta.workspace_default_members, &meta.workspace_members); } assert_eq!(meta.packages.len(), 9); @@ -456,6 +466,18 @@ fn all_the_fields() { kind.target.as_ref().map(|x| x.to_string()), Some("cfg(windows)".to_string()) ); + + let serialized = serde_json::to_value(meta).unwrap(); + if ver >= semver::Version::parse("1.71.0").unwrap() { + assert!(serialized.as_object().unwrap()["workspace_default_members"] + .as_array() + .is_some()); + } else { + assert!(!serialized + .as_object() + .unwrap() + .contains_key("workspace_default_members")); + } } #[test] @@ -696,3 +718,10 @@ fn debuginfo_variants() { } } } + +#[test] +#[should_panic = "WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71"] +fn missing_workspace_default_members() { + let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).unwrap(); + let _ = &*meta.workspace_default_members; +}