From 9dd8a2c39e44a422a413f894cf86468bb2b243ad Mon Sep 17 00:00:00 2001 From: darkraider01 <95766741+darkraider01@users.noreply.github.com> Date: Sat, 26 Sep 2026 02:11:35 +0530 Subject: [PATCH 1/3] test(metadata): test cargo metadata behavior with feature metadata --- tests/testsuite/metadata.rs | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index adb0562dc12..60c3a7da405 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -4984,3 +4984,65 @@ fn metadata_ignores_build_target_configuration() -> anyhow::Result<()> { ); Ok(()) } + +#[cargo_test] +fn cargo_metadata_features_v2() { + Package::new("serde", "1.0.0").publish(); + Package::new("log", "1.0.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["feature-metadata"] + + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies] + serde = { version = "1.0.0", optional = true } + log = { version = "1.0.0", optional = true } + + [features] + default = ["serde"] + empty = [] + legacy = ["empty"] + serde = { enables = ["dep:serde"], doc = "Enables support for serialization via serde." } + table_empty = { enables = [] } + table_without_doc = { enables = ["empty"] } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("metadata --format-version 1") + .masquerade_as_nightly_cargo(&["feature-metadata"]) + .with_stdout_data( + str![[r#" +{ + "packages": [ + { + "name": "foo", + "features": { + "default": ["serde"], + "empty": [], + "legacy": ["empty"], + "log": ["dep:log"], + "serde": ["dep:serde"], + "table_empty": [], + "table_without_doc": ["empty"] + }, + "...": "{...}" + }, + "{...}", + "{...}" + ], + "...": "{...}" +} +"#]] + .is_json(), + ) + .run(); +} From a0ce04ed0724133df0bfcadd4cb2df0d26a5f3e6 Mon Sep 17 00:00:00 2001 From: darkraider01 <95766741+darkraider01@users.noreply.github.com> Date: Sat, 26 Sep 2026 02:11:55 +0530 Subject: [PATCH 2/3] feat(metadata): mirror package features in features_v2 --- src/workspace/package.rs | 46 +++++++++++++++++++++++++++++-------- tests/testsuite/metadata.rs | 12 ++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/workspace/package.rs b/src/workspace/package.rs index 378e9fdb705..3931d583e20 100644 --- a/src/workspace/package.rs +++ b/src/workspace/package.rs @@ -10,7 +10,7 @@ use std::time::{Duration, Instant}; use crate::util::data_structures::{HashMap, HashSet}; use anyhow::Context as _; -use cargo_util_schemas::manifest::{Hints, RustVersion}; +use cargo_util_schemas::manifest::{FeatureDefinition, Hints, RustVersion}; use futures::FutureExt; use futures::TryStreamExt; use futures::stream::FuturesUnordered; @@ -31,8 +31,8 @@ use crate::util::network::retry::{Retry, RetryResult}; use crate::util::{self, GlobalContext, Progress, ProgressStyle, internal}; use crate::workspace::dependency::DepKind; use crate::workspace::{ - CliUnstable, Dependency, Features, Manifest, PackageId, PackageIdSpec, SerializedDependency, - SourceId, Target, + CliUnstable, Dependency, Feature, Features, Manifest, PackageId, PackageIdSpec, + SerializedDependency, SourceId, Target, }; use crate::workspace::{Summary, Workspace}; @@ -78,6 +78,8 @@ pub struct SerializedPackage { dependencies: Vec, targets: Vec, features: BTreeMap>, + #[serde(skip_serializing_if = "Option::is_none")] + features_v2: Option>, manifest_path: PathBuf, metadata: Option, publish: Option>, @@ -98,6 +100,13 @@ pub struct SerializedPackage { hints: Option, } +#[derive(Serialize)] +struct SerializedFeature { + enables: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + doc: Option, +} + impl Package { /// Creates a package from a manifest and its location. pub fn new(manifest: Manifest, manifest_path: &Path) -> Package { @@ -210,12 +219,30 @@ impl Package { .filter(|t| t.src_path().is_path()) .cloned() .collect(); - // Convert Vec to Vec - let crate_features = summary - .features() - .iter() - .map(|(k, v)| (*k, v.iter().map(|fv| fv.to_string().into()).collect())) - .collect(); + let manifest_features = self.manifest().normalized_toml().features(); + let mut crate_features = BTreeMap::new(); + let mut features_v2 = cargo_features + .is_enabled(Feature::feature_metadata()) + .then(BTreeMap::new); + for (k, v) in summary.features() { + let enables: Vec = v.iter().map(|fv| fv.to_string().into()).collect(); + if let Some(features_v2) = &mut features_v2 { + let doc = manifest_features + .and_then(|mf| mf.get(k.as_str())) + .and_then(|feat_def| match feat_def { + FeatureDefinition::Metadata(meta) => meta.doc.clone(), + FeatureDefinition::Array(_) => None, + }); + features_v2.insert( + *k, + SerializedFeature { + enables: enables.clone(), + doc, + }, + ); + } + crate_features.insert(*k, enables); + } SerializedPackage { name: package_id.name(), @@ -232,6 +259,7 @@ impl Package { .collect(), targets, features: crate_features, + features_v2, manifest_path: self.manifest_path().to_path_buf(), metadata: self.manifest().custom_metadata().cloned(), authors: manmeta.authors.clone(), diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index 60c3a7da405..293b135f11d 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -5034,6 +5034,18 @@ fn cargo_metadata_features_v2() { "table_empty": [], "table_without_doc": ["empty"] }, + "features_v2": { + "default": { "enables": ["serde"] }, + "empty": { "enables": [] }, + "legacy": { "enables": ["empty"] }, + "log": { "enables": ["dep:log"] }, + "serde": { + "enables": ["dep:serde"], + "doc": "Enables support for serialization via serde." + }, + "table_empty": { "enables": [] }, + "table_without_doc": { "enables": ["empty"] } + }, "...": "{...}" }, "{...}", From 9441b56b3abacac3f5f4ac07356d970099aee648 Mon Sep 17 00:00:00 2001 From: darkraider01 <95766741+darkraider01@users.noreply.github.com> Date: Sat, 26 Sep 2026 02:12:04 +0530 Subject: [PATCH 3/3] docs(metadata): document features_v2 in unstable reference --- doc/book/src/reference/unstable.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/book/src/reference/unstable.md b/doc/book/src/reference/unstable.md index 2a0cb0cf495..fd35a51e0c4 100644 --- a/doc/book/src/reference/unstable.md +++ b/doc/book/src/reference/unstable.md @@ -2407,6 +2407,20 @@ foo = { enables = [] } The required `enables` field is equivalent to the array-of-strings syntax. +With `feature-metadata` enabled, `cargo metadata --format-version 1` includes a +`features_v2` field alongside the existing `features` field. It mirrors every +feature as an object with an `enables` array and other metadata fields: + +```json +"features_v2": { + "foo": { "enables": [] }, + "serde": { + "enables": ["dep:serde"], + "doc": "Enables support for serialization via serde." + } +} +``` + For other metadata fields, see the subsections below. ### feature-documentation