Skip to content

Commit d7e3b7f

Browse files
committed
Auto merge of #4562 - SimonSapin:btree-manifest, r=matklad
Make manifest serialization deterministic Fixes #4326 `cargo package` (and so `cargo publish`) parses a crate’s `Cargo.toml`, makes some modifications, and re-serializes it. Because the `TomlManifest` struct uses `HashMap` with its default `RandomState` hasher, the maps’ iteration order changed on every run. As a result, when using `cargo vendor`, updating a dependency would generate a diff larger than necessary, with non-significant order-changes obscuring significant changes. This replaces some uses of `HashMap` with `BTreeMap`, whose iteration order is deterministic (based on `Ord`).
2 parents a888e11 + f38c53f commit d7e3b7f

9 files changed

Lines changed: 89 additions & 42 deletions

File tree

Cargo.lock

Lines changed: 32 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cargo/core/manifest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{HashMap, BTreeMap};
22
use std::fmt;
33
use std::path::{PathBuf, Path};
44
use std::rc::Rc;
@@ -75,7 +75,7 @@ pub struct ManifestMetadata {
7575
pub homepage: Option<String>, // url
7676
pub repository: Option<String>, // url
7777
pub documentation: Option<String>, // url
78-
pub badges: HashMap<String, HashMap<String, String>>,
78+
pub badges: BTreeMap<String, BTreeMap<String, String>>,
7979
}
8080

8181
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]

src/cargo/core/package.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::cell::{Ref, RefCell};
2-
use std::collections::HashMap;
2+
use std::collections::{HashMap, BTreeMap};
33
use std::fmt;
44
use std::hash;
55
use std::path::{Path, PathBuf};
@@ -37,7 +37,7 @@ struct SerializedPackage<'a> {
3737
source: &'a SourceId,
3838
dependencies: &'a [Dependency],
3939
targets: &'a [Target],
40-
features: &'a HashMap<String, Vec<String>>,
40+
features: &'a BTreeMap<String, Vec<String>>,
4141
manifest_path: &'a str,
4242
}
4343

src/cargo/core/summary.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::BTreeMap;
22
use std::mem;
33
use std::rc::Rc;
44

@@ -20,14 +20,14 @@ pub struct Summary {
2020
struct Inner {
2121
package_id: PackageId,
2222
dependencies: Vec<Dependency>,
23-
features: HashMap<String, Vec<String>>,
23+
features: BTreeMap<String, Vec<String>>,
2424
checksum: Option<String>,
2525
}
2626

2727
impl Summary {
2828
pub fn new(pkg_id: PackageId,
2929
dependencies: Vec<Dependency>,
30-
features: HashMap<String, Vec<String>>) -> CargoResult<Summary> {
30+
features: BTreeMap<String, Vec<String>>) -> CargoResult<Summary> {
3131
for dep in dependencies.iter() {
3232
if features.get(dep.name()).is_some() {
3333
bail!("Features and dependencies cannot have the \
@@ -78,7 +78,7 @@ impl Summary {
7878
pub fn version(&self) -> &Version { self.package_id().version() }
7979
pub fn source_id(&self) -> &SourceId { self.package_id().source_id() }
8080
pub fn dependencies(&self) -> &[Dependency] { &self.inner.dependencies }
81-
pub fn features(&self) -> &HashMap<String, Vec<String>> { &self.inner.features }
81+
pub fn features(&self) -> &BTreeMap<String, Vec<String>> { &self.inner.features }
8282
pub fn checksum(&self) -> Option<&str> {
8383
self.inner.checksum.as_ref().map(|s| &s[..])
8484
}

src/cargo/sources/registry/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
//! ```
160160
161161
use std::borrow::Cow;
162-
use std::collections::HashMap;
162+
use std::collections::BTreeMap;
163163
use std::fmt;
164164
use std::fs::File;
165165
use std::path::{PathBuf, Path};
@@ -206,7 +206,7 @@ struct RegistryPackage<'a> {
206206
name: Cow<'a, str>,
207207
vers: Version,
208208
deps: DependencyList,
209-
features: HashMap<String, Vec<String>>,
209+
features: BTreeMap<String, Vec<String>>,
210210
cksum: String,
211211
yanked: Option<bool>,
212212
}

src/cargo/util/toml/mod.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::{HashMap, HashSet, BTreeSet};
1+
use std::collections::{HashMap, BTreeMap, HashSet, BTreeSet};
22
use std::fmt;
33
use std::fs;
44
use std::path::{Path, PathBuf};
@@ -207,21 +207,21 @@ pub struct TomlManifest {
207207
example: Option<Vec<TomlExampleTarget>>,
208208
test: Option<Vec<TomlTestTarget>>,
209209
bench: Option<Vec<TomlTestTarget>>,
210-
dependencies: Option<HashMap<String, TomlDependency>>,
210+
dependencies: Option<BTreeMap<String, TomlDependency>>,
211211
#[serde(rename = "dev-dependencies")]
212-
dev_dependencies: Option<HashMap<String, TomlDependency>>,
212+
dev_dependencies: Option<BTreeMap<String, TomlDependency>>,
213213
#[serde(rename = "dev_dependencies")]
214-
dev_dependencies2: Option<HashMap<String, TomlDependency>>,
214+
dev_dependencies2: Option<BTreeMap<String, TomlDependency>>,
215215
#[serde(rename = "build-dependencies")]
216-
build_dependencies: Option<HashMap<String, TomlDependency>>,
216+
build_dependencies: Option<BTreeMap<String, TomlDependency>>,
217217
#[serde(rename = "build_dependencies")]
218-
build_dependencies2: Option<HashMap<String, TomlDependency>>,
219-
features: Option<HashMap<String, Vec<String>>>,
220-
target: Option<HashMap<String, TomlPlatform>>,
221-
replace: Option<HashMap<String, TomlDependency>>,
222-
patch: Option<HashMap<String, HashMap<String, TomlDependency>>>,
218+
build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
219+
features: Option<BTreeMap<String, Vec<String>>>,
220+
target: Option<BTreeMap<String, TomlPlatform>>,
221+
replace: Option<BTreeMap<String, TomlDependency>>,
222+
patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>,
223223
workspace: Option<TomlWorkspace>,
224-
badges: Option<HashMap<String, HashMap<String, String>>>,
224+
badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
225225
#[serde(rename = "cargo-features")]
226226
cargo_features: Option<Vec<String>>,
227227
}
@@ -475,8 +475,8 @@ impl TomlManifest {
475475
cargo_features: self.cargo_features.clone(),
476476
};
477477

478-
fn map_deps(deps: Option<&HashMap<String, TomlDependency>>)
479-
-> Option<HashMap<String, TomlDependency>>
478+
fn map_deps(deps: Option<&BTreeMap<String, TomlDependency>>)
479+
-> Option<BTreeMap<String, TomlDependency>>
480480
{
481481
let deps = match deps {
482482
Some(deps) => deps,
@@ -557,7 +557,7 @@ impl TomlManifest {
557557

558558
fn process_dependencies(
559559
cx: &mut Context,
560-
new_deps: Option<&HashMap<String, TomlDependency>>,
560+
new_deps: Option<&BTreeMap<String, TomlDependency>>,
561561
kind: Option<Kind>)
562562
-> CargoResult<()>
563563
{
@@ -600,7 +600,7 @@ impl TomlManifest {
600600
}
601601

602602
{
603-
let mut names_sources = HashMap::new();
603+
let mut names_sources = BTreeMap::new();
604604
for dep in &deps {
605605
let name = dep.name();
606606
let prev = names_sources.insert(name, dep.source_id());
@@ -616,7 +616,7 @@ impl TomlManifest {
616616
let include = project.include.clone().unwrap_or_default();
617617

618618
let summary = Summary::new(pkgid, deps, me.features.clone()
619-
.unwrap_or_else(HashMap::new))?;
619+
.unwrap_or_else(BTreeMap::new))?;
620620
let metadata = ManifestMetadata {
621621
description: project.description.clone(),
622622
homepage: project.homepage.clone(),
@@ -985,15 +985,15 @@ impl ser::Serialize for PathValue {
985985
/// Corresponds to a `target` entry, but `TomlTarget` is already used.
986986
#[derive(Serialize, Deserialize, Debug)]
987987
struct TomlPlatform {
988-
dependencies: Option<HashMap<String, TomlDependency>>,
988+
dependencies: Option<BTreeMap<String, TomlDependency>>,
989989
#[serde(rename = "build-dependencies")]
990-
build_dependencies: Option<HashMap<String, TomlDependency>>,
990+
build_dependencies: Option<BTreeMap<String, TomlDependency>>,
991991
#[serde(rename = "build_dependencies")]
992-
build_dependencies2: Option<HashMap<String, TomlDependency>>,
992+
build_dependencies2: Option<BTreeMap<String, TomlDependency>>,
993993
#[serde(rename = "dev-dependencies")]
994-
dev_dependencies: Option<HashMap<String, TomlDependency>>,
994+
dev_dependencies: Option<BTreeMap<String, TomlDependency>>,
995995
#[serde(rename = "dev_dependencies")]
996-
dev_dependencies2: Option<HashMap<String, TomlDependency>>,
996+
dev_dependencies2: Option<BTreeMap<String, TomlDependency>>,
997997
}
998998

999999
impl TomlTarget {

src/crates-io/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extern crate serde_json;
88
#[macro_use]
99
extern crate serde_derive;
1010

11-
use std::collections::HashMap;
11+
use std::collections::BTreeMap;
1212
use std::fs::File;
1313
use std::io::prelude::*;
1414
use std::io::{self, Cursor};
@@ -76,7 +76,7 @@ pub struct NewCrate {
7676
pub name: String,
7777
pub vers: String,
7878
pub deps: Vec<NewCrateDependency>,
79-
pub features: HashMap<String, Vec<String>>,
79+
pub features: BTreeMap<String, Vec<String>>,
8080
pub authors: Vec<String>,
8181
pub description: Option<String>,
8282
pub documentation: Option<String>,
@@ -87,7 +87,7 @@ pub struct NewCrate {
8787
pub license: Option<String>,
8888
pub license_file: Option<String>,
8989
pub repository: Option<String>,
90-
pub badges: HashMap<String, HashMap<String, String>>,
90+
pub badges: BTreeMap<String, BTreeMap<String, String>>,
9191
}
9292

9393
#[derive(Serialize)]

tests/package.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,9 @@ to proceed despite this, pass the `--allow-dirty` flag
700700

701701
#[test]
702702
fn generated_manifest() {
703+
Package::new("abc", "1.0.0").publish();
704+
Package::new("def", "1.0.0").publish();
705+
Package::new("ghi", "1.0.0").publish();
703706
let p = project("foo")
704707
.file("Cargo.toml", r#"
705708
[project]
@@ -717,6 +720,9 @@ fn generated_manifest() {
717720
718721
[dependencies]
719722
bar = { path = "bar", version = "0.1" }
723+
def = "1.0"
724+
ghi = "1.0"
725+
abc = "1.0"
720726
"#)
721727
.file("src/main.rs", "")
722728
.file("bar/Cargo.toml", r#"
@@ -741,6 +747,8 @@ fn generated_manifest() {
741747
.unwrap();
742748
let mut contents = String::new();
743749
entry.read_to_string(&mut contents).unwrap();
750+
// BTreeMap makes the order of dependencies in the generated file deterministic
751+
// by sorting alphabetically
744752
assert_that(&contents[..], equal_to(
745753
r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
746754
#
@@ -764,8 +772,17 @@ license = "MIT"
764772
765773
[package.metadata]
766774
foo = "bar"
775+
[dependencies.abc]
776+
version = "1.0"
777+
767778
[dependencies.bar]
768779
version = "0.1"
780+
781+
[dependencies.def]
782+
version = "1.0"
783+
784+
[dependencies.ghi]
785+
version = "1.0"
769786
"#));
770787
}
771788

tests/resolve.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
extern crate hamcrest;
44
extern crate cargo;
55

6-
use std::collections::HashMap;
6+
use std::collections::BTreeMap;
77

88
use hamcrest::{assert_that, equal_to, contains, not};
99

@@ -32,7 +32,7 @@ fn resolve(pkg: &PackageId, deps: Vec<Dependency>, registry: &[Summary])
3232
fn requires_precise(&self) -> bool { false }
3333
}
3434
let mut registry = MyRegistry(registry);
35-
let summary = Summary::new(pkg.clone(), deps, HashMap::new()).unwrap();
35+
let summary = Summary::new(pkg.clone(), deps, BTreeMap::new()).unwrap();
3636
let method = Method::Everything;
3737
let resolve = resolver::resolve(&[(summary, method)], &[], &mut registry, None)?;
3838
let res = resolve.iter().cloned().collect();
@@ -78,11 +78,11 @@ macro_rules! pkg {
7878
($pkgid:expr => [$($deps:expr),+]) => ({
7979
let d: Vec<Dependency> = vec![$($deps.to_dep()),+];
8080

81-
Summary::new($pkgid.to_pkgid(), d, HashMap::new()).unwrap()
81+
Summary::new($pkgid.to_pkgid(), d, BTreeMap::new()).unwrap()
8282
});
8383

8484
($pkgid:expr) => (
85-
Summary::new($pkgid.to_pkgid(), Vec::new(), HashMap::new()).unwrap()
85+
Summary::new($pkgid.to_pkgid(), Vec::new(), BTreeMap::new()).unwrap()
8686
)
8787
}
8888

@@ -92,7 +92,7 @@ fn registry_loc() -> SourceId {
9292
}
9393

9494
fn pkg(name: &str) -> Summary {
95-
Summary::new(pkg_id(name), Vec::new(), HashMap::new()).unwrap()
95+
Summary::new(pkg_id(name), Vec::new(), BTreeMap::new()).unwrap()
9696
}
9797

9898
fn pkg_id(name: &str) -> PackageId {
@@ -108,7 +108,7 @@ fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
108108
}
109109

110110
fn pkg_loc(name: &str, loc: &str) -> Summary {
111-
Summary::new(pkg_id_loc(name, loc), Vec::new(), HashMap::new()).unwrap()
111+
Summary::new(pkg_id_loc(name, loc), Vec::new(), BTreeMap::new()).unwrap()
112112
}
113113

114114
fn dep(name: &str) -> Dependency { dep_req(name, "1.0.0") }

0 commit comments

Comments
 (0)