Skip to content

Commit af99add

Browse files
feat(manifest)!: implement feature-metadata RFC3416
1 parent b414a91 commit af99add

File tree

4 files changed

+112
-32
lines changed

4 files changed

+112
-32
lines changed

crates/cargo-util-schemas/src/manifest/mod.rs

+59-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub struct TomlManifest {
4141
pub package: Option<Box<TomlPackage>>,
4242
pub project: Option<Box<TomlPackage>>,
4343
pub badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
44-
pub features: Option<BTreeMap<FeatureName, Vec<String>>>,
44+
pub features: Option<BTreeMap<FeatureName, FeatureDefinition>>,
4545
pub lib: Option<TomlLibTarget>,
4646
pub bin: Option<Vec<TomlBinTarget>>,
4747
pub example: Option<Vec<TomlExampleTarget>>,
@@ -110,7 +110,7 @@ impl TomlManifest {
110110
.or(self.build_dependencies2.as_ref())
111111
}
112112

113-
pub fn features(&self) -> Option<&BTreeMap<FeatureName, Vec<String>>> {
113+
pub fn features(&self) -> Option<&BTreeMap<FeatureName, FeatureDefinition>> {
114114
self.features.as_ref()
115115
}
116116

@@ -1521,6 +1521,63 @@ impl TomlPlatform {
15211521
}
15221522
}
15231523

1524+
/// Definition of a feature.
1525+
#[derive(Clone, Debug, Serialize)]
1526+
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1527+
#[serde(untagged)]
1528+
pub enum FeatureDefinition {
1529+
/// Features that this feature enables.
1530+
Array(Vec<String>),
1531+
/// Metadata of this feature.
1532+
Metadata(FeatureMetadata),
1533+
}
1534+
1535+
// Implementing `Deserialize` manually allows for a better error message when the `enables` key is
1536+
// missing.
1537+
impl<'de> de::Deserialize<'de> for FeatureDefinition {
1538+
fn deserialize<D>(d: D) -> Result<FeatureDefinition, D::Error>
1539+
where
1540+
D: de::Deserializer<'de>,
1541+
{
1542+
UntaggedEnumVisitor::new()
1543+
.seq(|seq| {
1544+
seq.deserialize::<Vec<String>>()
1545+
.map(FeatureDefinition::Array)
1546+
})
1547+
.map(|seq| {
1548+
seq.deserialize::<FeatureMetadata>()
1549+
.map(FeatureDefinition::Metadata)
1550+
})
1551+
.deserialize(d)
1552+
}
1553+
}
1554+
1555+
impl FeatureDefinition {
1556+
/// Returns the features that this feature enables.
1557+
pub fn enables(&self) -> &[String] {
1558+
match self {
1559+
Self::Array(features) => features,
1560+
Self::Metadata(FeatureMetadata {
1561+
enables: features, ..
1562+
}) => features,
1563+
}
1564+
}
1565+
}
1566+
1567+
/// Metadata of a feature.
1568+
#[derive(Clone, Debug, Deserialize, Serialize)]
1569+
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1570+
pub struct FeatureMetadata {
1571+
/// Features that this feature enables.
1572+
pub enables: Vec<String>,
1573+
1574+
/// This is here to provide a way to see the "unused manifest keys" when deserializing
1575+
#[serde(skip_serializing)]
1576+
#[serde(flatten)]
1577+
#[cfg_attr(feature = "unstable-schema", schemars(skip))]
1578+
pub _unused_keys: BTreeMap<String, toml::Value>,
1579+
}
1580+
15241581
#[derive(Serialize, Debug, Clone)]
15251582
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
15261583
pub struct InheritableLints {

src/cargo/ops/registry/publish.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ pub(crate) fn prepare_transmit(
539539
.map(|(feat, values)| {
540540
(
541541
feat.to_string(),
542-
values.iter().map(|fv| fv.to_string()).collect(),
542+
values.enables().iter().map(|fv| fv.to_string()).collect(),
543543
)
544544
})
545545
.collect::<BTreeMap<String, Vec<String>>>(),

src/cargo/util/toml/mod.rs

+47-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use anyhow::{anyhow, bail, Context as _};
1111
use cargo_platform::Platform;
1212
use cargo_util::paths;
1313
use cargo_util_schemas::manifest::{
14-
self, PackageName, PathBaseName, TomlDependency, TomlDetailedDependency, TomlManifest,
14+
self, FeatureDefinition, FeatureMetadata, FeatureName, PackageName, PathBaseName,
15+
TomlDependency, TomlDetailedDependency, TomlManifest,
1516
};
1617
use cargo_util_schemas::manifest::{RustVersion, StringOrBool};
1718
use itertools::Itertools;
@@ -708,8 +709,8 @@ fn default_readme_from_package_root(package_root: &Path) -> Option<String> {
708709

709710
#[tracing::instrument(skip_all)]
710711
fn normalize_features(
711-
original_features: Option<&BTreeMap<manifest::FeatureName, Vec<String>>>,
712-
) -> CargoResult<Option<BTreeMap<manifest::FeatureName, Vec<String>>>> {
712+
original_features: Option<&BTreeMap<manifest::FeatureName, FeatureDefinition>>,
713+
) -> CargoResult<Option<BTreeMap<manifest::FeatureName, FeatureDefinition>>> {
713714
let Some(normalized_features) = original_features.cloned() else {
714715
return Ok(None);
715716
};
@@ -1296,6 +1297,8 @@ pub fn to_real_manifest(
12961297
}
12971298
}
12981299

1300+
validate_feature_definitions(original_toml.features.as_ref(), warnings)?;
1301+
12991302
validate_dependencies(original_toml.dependencies.as_ref(), None, None, warnings)?;
13001303
validate_dependencies(
13011304
original_toml.dev_dependencies(),
@@ -1497,7 +1500,7 @@ pub fn to_real_manifest(
14971500
.map(|(k, v)| {
14981501
(
14991502
InternedString::new(k),
1500-
v.iter().map(InternedString::from).collect(),
1503+
v.enables().iter().map(InternedString::from).collect(),
15011504
)
15021505
})
15031506
.collect(),
@@ -1764,6 +1767,30 @@ fn to_virtual_manifest(
17641767
Ok(manifest)
17651768
}
17661769

1770+
fn validate_feature_definitions(
1771+
features: Option<&BTreeMap<FeatureName, FeatureDefinition>>,
1772+
warnings: &mut Vec<String>,
1773+
) -> CargoResult<()> {
1774+
let Some(features) = features else {
1775+
return Ok(());
1776+
};
1777+
1778+
for (feature, feature_definition) in features {
1779+
match feature_definition {
1780+
FeatureDefinition::Array(..) => {}
1781+
FeatureDefinition::Metadata(FeatureMetadata { _unused_keys, .. }) => {
1782+
warnings.extend(
1783+
_unused_keys
1784+
.keys()
1785+
.map(|k| format!("unused manifest key: `features.{feature}.{k}`")),
1786+
);
1787+
}
1788+
}
1789+
}
1790+
1791+
Ok(())
1792+
}
1793+
17671794
#[tracing::instrument(skip_all)]
17681795
fn validate_dependencies(
17691796
original_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
@@ -2902,16 +2929,23 @@ fn prepare_toml_for_publish(
29022929
};
29032930

29042931
features.values_mut().for_each(|feature_deps| {
2905-
feature_deps.retain(|feature_dep| {
2906-
let feature_value = FeatureValue::new(InternedString::new(feature_dep));
2907-
match feature_value {
2908-
FeatureValue::Dep { dep_name } | FeatureValue::DepFeature { dep_name, .. } => {
2909-
let k = &manifest::PackageName::new(dep_name.to_string()).unwrap();
2910-
dep_name_set.contains(k)
2932+
let feature_array = feature_deps
2933+
.enables()
2934+
.iter()
2935+
.filter(|feature_dep| {
2936+
let feature_value = FeatureValue::new(InternedString::new(feature_dep));
2937+
match feature_value {
2938+
FeatureValue::Dep { dep_name }
2939+
| FeatureValue::DepFeature { dep_name, .. } => {
2940+
let k = &manifest::PackageName::new(dep_name.to_string()).unwrap();
2941+
dep_name_set.contains(k)
2942+
}
2943+
_ => true,
29112944
}
2912-
_ => true,
2913-
}
2914-
});
2945+
})
2946+
.cloned()
2947+
.collect();
2948+
*feature_deps = FeatureDefinition::Array(feature_array);
29152949
});
29162950
}
29172951

tests/testsuite/features.rs

+5-16
Original file line numberDiff line numberDiff line change
@@ -2362,7 +2362,7 @@ fn unused_keys_in_feature_metadata() {
23622362
edition = "2015"
23632363
23642364
[features]
2365-
foo = { enables = ["bar"], foobar = false }
2365+
foo = { enables = ["bar"], a = false, b = true }
23662366
bar = []
23672367
"#,
23682368
)
@@ -2371,7 +2371,8 @@ fn unused_keys_in_feature_metadata() {
23712371

23722372
p.cargo("check")
23732373
.with_stderr_data(str![[r#"
2374-
[WARNING] unused manifest key: `features.foo.foobar`
2374+
[WARNING] unused manifest key: `features.foo.a`
2375+
[WARNING] unused manifest key: `features.foo.b`
23752376
[CHECKING] foo v0.0.0 ([ROOT]/foo)
23762377
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
23772378
@@ -2395,22 +2396,10 @@ fn normalize_feature_metadata() {
23952396
c = { enables = ["a", "b"] }
23962397
"#,
23972398
)
2398-
.file(
2399-
"src/main.rs",
2400-
r#"
2401-
#[cfg(feature = "a")]
2402-
fn a() {}
2403-
#[cfg(feature = "b")]
2404-
fn b() {}
2405-
fn main() {
2406-
a();
2407-
b();
2408-
}
2409-
"#,
2410-
)
2399+
.file("src/main.rs", "")
24112400
.build();
24122401

2413-
p.cargo("package --no-verify -v").run();
2402+
p.cargo("package --no-verify").run();
24142403
let f = File::open(&p.root().join("target/package/foo-0.0.0.crate")).unwrap();
24152404
let normalized_manifest = str![[r#"
24162405
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

0 commit comments

Comments
 (0)