Skip to content

Commit be28b58

Browse files
committed
Add features2 to the index.
1 parent 196673b commit be28b58

File tree

6 files changed

+755
-8
lines changed

6 files changed

+755
-8
lines changed

crates/cargo-test-support/src/registry.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cargo::sources::CRATES_IO_INDEX;
44
use cargo::util::Sha256;
55
use flate2::write::GzEncoder;
66
use flate2::Compression;
7-
use std::collections::HashMap;
7+
use std::collections::BTreeMap;
88
use std::fmt::Write as _;
99
use std::fs::{self, File};
1010
use std::io::{BufRead, BufReader, Write};
@@ -319,7 +319,7 @@ pub struct Package {
319319
deps: Vec<Dependency>,
320320
files: Vec<PackageFile>,
321321
yanked: bool,
322-
features: HashMap<String, Vec<String>>,
322+
features: FeatureMap,
323323
local: bool,
324324
alternative: bool,
325325
invalid_json: bool,
@@ -330,6 +330,8 @@ pub struct Package {
330330
v: Option<u32>,
331331
}
332332

333+
type FeatureMap = BTreeMap<String, Vec<String>>;
334+
333335
#[derive(Clone)]
334336
pub struct Dependency {
335337
name: String,
@@ -394,7 +396,7 @@ impl Package {
394396
deps: Vec::new(),
395397
files: Vec::new(),
396398
yanked: false,
397-
features: HashMap::new(),
399+
features: BTreeMap::new(),
398400
local: false,
399401
alternative: false,
400402
invalid_json: false,
@@ -609,15 +611,21 @@ impl Package {
609611
} else {
610612
serde_json::json!(self.name)
611613
};
614+
// This emulates what crates.io may do in the future.
615+
let (features, features2) = split_index_features(self.features.clone());
612616
let mut json = serde_json::json!({
613617
"name": name,
614618
"vers": self.vers,
615619
"deps": deps,
616620
"cksum": cksum,
617-
"features": self.features,
621+
"features": features,
618622
"yanked": self.yanked,
619623
"links": self.links,
620624
});
625+
if let Some(f2) = &features2 {
626+
json["features2"] = serde_json::json!(f2);
627+
json["v"] = serde_json::json!(2);
628+
}
621629
if let Some(v) = self.v {
622630
json["v"] = serde_json::json!(v);
623631
}
@@ -850,3 +858,21 @@ impl Dependency {
850858
self
851859
}
852860
}
861+
862+
fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
863+
let mut features2 = FeatureMap::new();
864+
for (feat, values) in features.iter_mut() {
865+
if values
866+
.iter()
867+
.any(|value| value.starts_with("dep:") || value.contains("?/"))
868+
{
869+
let new_values = values.drain(..).collect();
870+
features2.insert(feat.clone(), new_values);
871+
}
872+
}
873+
if features2.is_empty() {
874+
(features, None)
875+
} else {
876+
(features, Some(features2))
877+
}
878+
}

src/cargo/sources/registry/index.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,11 @@ impl<'cfg> RegistryIndex<'cfg> {
308308
// minimize the amount of work being done here and parse as little as
309309
// necessary.
310310
let raw_data = &summaries.raw_data;
311-
let max_version = 1;
311+
let max_version = if namespaced_features || weak_dep_features {
312+
2
313+
} else {
314+
1
315+
};
312316
Ok(summaries
313317
.versions
314318
.iter_mut()
@@ -770,7 +774,8 @@ impl IndexSummary {
770774
vers,
771775
cksum,
772776
deps,
773-
features,
777+
mut features,
778+
features2,
774779
yanked,
775780
links,
776781
v,
@@ -782,6 +787,11 @@ impl IndexSummary {
782787
.into_iter()
783788
.map(|dep| dep.into_dep(source_id))
784789
.collect::<CargoResult<Vec<_>>>()?;
790+
if let Some(features2) = features2 {
791+
for (name, values) in features2 {
792+
features.entry(name).or_default().extend(values);
793+
}
794+
}
785795
let mut summary = Summary::new(config, pkgid, deps, &features, links)?;
786796
summary.set_checksum(cksum);
787797
Ok(IndexSummary {

src/cargo/sources/registry/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ pub struct RegistryPackage<'a> {
258258
#[serde(borrow)]
259259
deps: Vec<RegistryDependency<'a>>,
260260
features: BTreeMap<InternedString, Vec<InternedString>>,
261+
/// This field contains features with new, extended syntax. Specifically,
262+
/// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`).
263+
///
264+
/// This is separated from `features` because versions older than 1.19
265+
/// will fail to load due to not being able to parse the new syntax, even
266+
/// with a `Cargo.lock` file.
267+
features2: Option<BTreeMap<InternedString, Vec<InternedString>>>,
261268
cksum: String,
262269
/// If `true`, Cargo will skip this version when resolving.
263270
///
@@ -274,10 +281,12 @@ pub struct RegistryPackage<'a> {
274281
/// If this is None, it defaults to version 1. Entries with unknown
275282
/// versions are ignored.
276283
///
284+
/// Version `2` format adds the `features2` field.
285+
///
277286
/// This provides a method to safely introduce changes to index entries
278287
/// and allow older versions of cargo to ignore newer entries it doesn't
279288
/// understand. This is honored as of 1.51, so unfortunately older
280-
/// versions will ignore it, and potentially misinterpret version 1 and
289+
/// versions will ignore it, and potentially misinterpret version 2 and
281290
/// newer entries.
282291
///
283292
/// The intent is that versions older than 1.51 will work with a

tests/testsuite/features_namespaced.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,3 +1106,105 @@ feat = ["opt-dep1"]
11061106
)],
11071107
);
11081108
}
1109+
1110+
#[cargo_test]
1111+
fn publish() {
1112+
// Publish behavior with explicit dep: syntax.
1113+
Package::new("bar", "1.0.0").publish();
1114+
let p = project()
1115+
.file(
1116+
"Cargo.toml",
1117+
r#"
1118+
[package]
1119+
name = "foo"
1120+
version = "0.1.0"
1121+
description = "foo"
1122+
license = "MIT"
1123+
homepage = "https://example.com/"
1124+
1125+
[dependencies]
1126+
bar = { version = "1.0", optional = true }
1127+
1128+
[features]
1129+
feat1 = []
1130+
feat2 = ["dep:bar"]
1131+
feat3 = ["feat2"]
1132+
"#,
1133+
)
1134+
.file("src/lib.rs", "")
1135+
.build();
1136+
1137+
p.cargo("publish --token sekrit -Z namespaced-features")
1138+
.masquerade_as_nightly_cargo()
1139+
.with_stderr(
1140+
"\
1141+
[UPDATING] [..]
1142+
[PACKAGING] foo v0.1.0 [..]
1143+
[VERIFYING] foo v0.1.0 [..]
1144+
[COMPILING] foo v0.1.0 [..]
1145+
[FINISHED] [..]
1146+
[UPLOADING] foo v0.1.0 [..]
1147+
",
1148+
)
1149+
.run();
1150+
1151+
publish::validate_upload_with_contents(
1152+
r#"
1153+
{
1154+
"authors": [],
1155+
"badges": {},
1156+
"categories": [],
1157+
"deps": [
1158+
{
1159+
"default_features": true,
1160+
"features": [],
1161+
"kind": "normal",
1162+
"name": "bar",
1163+
"optional": true,
1164+
"registry": "https://github.com/rust-lang/crates.io-index",
1165+
"target": null,
1166+
"version_req": "^1.0"
1167+
}
1168+
],
1169+
"description": "foo",
1170+
"documentation": null,
1171+
"features": {
1172+
"feat1": [],
1173+
"feat2": ["dep:bar"],
1174+
"feat3": ["feat2"]
1175+
},
1176+
"homepage": "https://example.com/",
1177+
"keywords": [],
1178+
"license": "MIT",
1179+
"license_file": null,
1180+
"links": null,
1181+
"name": "foo",
1182+
"readme": null,
1183+
"readme_file": null,
1184+
"repository": null,
1185+
"vers": "0.1.0"
1186+
}
1187+
"#,
1188+
"foo-0.1.0.crate",
1189+
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
1190+
&[(
1191+
"Cargo.toml",
1192+
r#"[..]
1193+
[package]
1194+
name = "foo"
1195+
version = "0.1.0"
1196+
description = "foo"
1197+
homepage = "https://example.com/"
1198+
license = "MIT"
1199+
[dependencies.bar]
1200+
version = "1.0"
1201+
optional = true
1202+
1203+
[features]
1204+
feat1 = []
1205+
feat2 = ["dep:bar"]
1206+
feat3 = ["feat2"]
1207+
"#,
1208+
)],
1209+
);
1210+
}

0 commit comments

Comments
 (0)