Skip to content

Commit e37b35c

Browse files
committed
Auto merge of #8489 - arlosi:deterministic_metadata, r=alexcrichton
Make `cargo metadata` output deterministic Uses BTreeMap instead of HashMap for the `cargo metadata` command, ensuring the output is sorted. The change did not cause a measurable performance impact for running `cargo metadata` on `cargo` itself. Fixes #8477
2 parents 1bc6e45 + 58869e5 commit e37b35c

File tree

5 files changed

+247
-261
lines changed

5 files changed

+247
-261
lines changed

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

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,7 +1444,7 @@ fn lines_match_works() {
14441444
/// You can use `[..]` wildcard in strings (useful for OS-dependent things such
14451445
/// as paths). You can use a `"{...}"` string literal as a wildcard for
14461446
/// arbitrary nested JSON (useful for parts of object emitted by other programs
1447-
/// (e.g., rustc) rather than Cargo itself). Arrays are sorted before comparison.
1447+
/// (e.g., rustc) rather than Cargo itself).
14481448
pub fn find_json_mismatch(expected: &Value, actual: &Value) -> Result<(), String> {
14491449
match find_json_mismatch_r(expected, actual) {
14501450
Some((expected_part, actual_part)) => Err(format!(
@@ -1472,26 +1472,10 @@ fn find_json_mismatch_r<'a>(
14721472
return Some((expected, actual));
14731473
}
14741474

1475-
let mut l = l.iter().collect::<Vec<_>>();
1476-
let mut r = r.iter().collect::<Vec<_>>();
1477-
1478-
l.retain(
1479-
|l| match r.iter().position(|r| find_json_mismatch_r(l, r).is_none()) {
1480-
Some(i) => {
1481-
r.remove(i);
1482-
false
1483-
}
1484-
None => true,
1485-
},
1486-
);
1487-
1488-
if !l.is_empty() {
1489-
assert!(!r.is_empty());
1490-
Some((l[0], r[0]))
1491-
} else {
1492-
assert_eq!(r.len(), 0);
1493-
None
1494-
}
1475+
l.iter()
1476+
.zip(r.iter())
1477+
.filter_map(|(l, r)| find_json_mismatch_r(l, r))
1478+
.next()
14951479
}
14961480
(&Object(ref l), &Object(ref r)) => {
14971481
let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::util::interning::InternedString;
77
use crate::util::CargoResult;
88
use cargo_platform::Platform;
99
use serde::Serialize;
10-
use std::collections::HashMap;
10+
use std::collections::BTreeMap;
1111
use std::path::PathBuf;
1212

1313
const VERSION: u32 = 1;
@@ -131,7 +131,7 @@ fn build_resolve_graph(
131131
// Download all Packages. This is needed to serialize the information
132132
// for every package. In theory this could honor target filtering,
133133
// but that would be somewhat complex.
134-
let mut package_map: HashMap<PackageId, Package> = ws_resolve
134+
let package_map: BTreeMap<PackageId, Package> = ws_resolve
135135
.pkg_set
136136
.get_many(ws_resolve.pkg_set.package_ids())?
137137
.into_iter()
@@ -141,7 +141,7 @@ fn build_resolve_graph(
141141

142142
// Start from the workspace roots, and recurse through filling out the
143143
// map, filtering targets as necessary.
144-
let mut node_map = HashMap::new();
144+
let mut node_map = BTreeMap::new();
145145
for member_pkg in ws.members() {
146146
build_resolve_graph_r(
147147
&mut node_map,
@@ -154,21 +154,22 @@ fn build_resolve_graph(
154154
}
155155
// Get a Vec of Packages.
156156
let actual_packages = package_map
157-
.drain()
157+
.into_iter()
158158
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
159159
.collect();
160+
160161
let mr = MetadataResolve {
161-
nodes: node_map.drain().map(|(_pkg_id, node)| node).collect(),
162+
nodes: node_map.into_iter().map(|(_pkg_id, node)| node).collect(),
162163
root: ws.current_opt().map(|pkg| pkg.package_id()),
163164
};
164165
Ok((actual_packages, mr))
165166
}
166167

167168
fn build_resolve_graph_r(
168-
node_map: &mut HashMap<PackageId, MetadataResolveNode>,
169+
node_map: &mut BTreeMap<PackageId, MetadataResolveNode>,
169170
pkg_id: PackageId,
170171
resolve: &Resolve,
171-
package_map: &HashMap<PackageId, Package>,
172+
package_map: &BTreeMap<PackageId, Package>,
172173
target_data: &RustcTargetData,
173174
requested_kinds: &[CompileKind],
174175
) {
@@ -190,7 +191,8 @@ fn build_resolve_graph_r(
190191
}
191192
})
192193
.filter_map(|(dep_id, deps)| {
193-
let dep_kinds: Vec<_> = deps.iter().map(DepKindInfo::from).collect();
194+
let mut dep_kinds: Vec<_> = deps.iter().map(DepKindInfo::from).collect();
195+
dep_kinds.sort();
194196
package_map
195197
.get(&dep_id)
196198
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))

tests/testsuite/alt_registry.rs

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,41 @@ fn alt_reg_metadata() {
836836
r#"
837837
{
838838
"packages": [
839+
{
840+
"name": "altdep",
841+
"version": "0.0.1",
842+
"id": "altdep 0.0.1 (registry+file:[..]/alternative-registry)",
843+
"license": null,
844+
"license_file": null,
845+
"description": null,
846+
"source": "registry+file:[..]/alternative-registry",
847+
"dependencies": [
848+
{
849+
"name": "bar",
850+
"source": "registry+https://github.com/rust-lang/crates.io-index",
851+
"req": "^0.0.1",
852+
"kind": null,
853+
"rename": null,
854+
"optional": false,
855+
"uses_default_features": true,
856+
"features": [],
857+
"target": null,
858+
"registry": null
859+
}
860+
],
861+
"targets": "{...}",
862+
"features": {},
863+
"manifest_path": "[..]/altdep-0.0.1/Cargo.toml",
864+
"metadata": null,
865+
"publish": null,
866+
"authors": [],
867+
"categories": [],
868+
"keywords": [],
869+
"readme": null,
870+
"repository": null,
871+
"edition": "2015",
872+
"links": null
873+
},
839874
{
840875
"name": "altdep2",
841876
"version": "0.0.1",
@@ -859,30 +894,17 @@ fn alt_reg_metadata() {
859894
"links": null
860895
},
861896
{
862-
"name": "altdep",
897+
"name": "bar",
863898
"version": "0.0.1",
864-
"id": "altdep 0.0.1 (registry+file:[..]/alternative-registry)",
899+
"id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
865900
"license": null,
866901
"license_file": null,
867902
"description": null,
868-
"source": "registry+file:[..]/alternative-registry",
869-
"dependencies": [
870-
{
871-
"name": "bar",
872-
"source": "registry+https://github.com/rust-lang/crates.io-index",
873-
"req": "^0.0.1",
874-
"kind": null,
875-
"rename": null,
876-
"optional": false,
877-
"uses_default_features": true,
878-
"features": [],
879-
"target": null,
880-
"registry": null
881-
}
882-
],
903+
"source": "registry+https://github.com/rust-lang/crates.io-index",
904+
"dependencies": [],
883905
"targets": "{...}",
884906
"features": {},
885-
"manifest_path": "[..]/altdep-0.0.1/Cargo.toml",
907+
"manifest_path": "[..]/bar-0.0.1/Cargo.toml",
886908
"metadata": null,
887909
"publish": null,
888910
"authors": [],
@@ -974,28 +996,6 @@ fn alt_reg_metadata() {
974996
"repository": null,
975997
"edition": "2015",
976998
"links": null
977-
},
978-
{
979-
"name": "bar",
980-
"version": "0.0.1",
981-
"id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
982-
"license": null,
983-
"license_file": null,
984-
"description": null,
985-
"source": "registry+https://github.com/rust-lang/crates.io-index",
986-
"dependencies": [],
987-
"targets": "{...}",
988-
"features": {},
989-
"manifest_path": "[..]/bar-0.0.1/Cargo.toml",
990-
"metadata": null,
991-
"publish": null,
992-
"authors": [],
993-
"categories": [],
994-
"keywords": [],
995-
"readme": null,
996-
"repository": null,
997-
"edition": "2015",
998-
"links": null
999999
}
10001000
],
10011001
"workspace_members": [
@@ -1056,14 +1056,27 @@ fn unknown_registry() {
10561056
{
10571057
"packages": [
10581058
{
1059-
"name": "baz",
1059+
"name": "bar",
10601060
"version": "0.0.1",
1061-
"id": "baz 0.0.1 (registry+file://[..]/alternative-registry)",
1061+
"id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
10621062
"license": null,
10631063
"license_file": null,
10641064
"description": null,
1065-
"source": "registry+file://[..]/alternative-registry",
1066-
"dependencies": [],
1065+
"source": "registry+https://github.com/rust-lang/crates.io-index",
1066+
"dependencies": [
1067+
{
1068+
"name": "baz",
1069+
"source": "registry+file://[..]/alternative-registry",
1070+
"req": "^0.0.1",
1071+
"kind": null,
1072+
"rename": null,
1073+
"optional": false,
1074+
"uses_default_features": true,
1075+
"features": [],
1076+
"target": null,
1077+
"registry": "file:[..]/alternative-registry"
1078+
}
1079+
],
10671080
"targets": "{...}",
10681081
"features": {},
10691082
"manifest_path": "[..]",
@@ -1078,30 +1091,17 @@ fn unknown_registry() {
10781091
"links": null
10791092
},
10801093
{
1081-
"name": "foo",
1094+
"name": "baz",
10821095
"version": "0.0.1",
1083-
"id": "foo 0.0.1 (path+file://[..]/foo)",
1096+
"id": "baz 0.0.1 (registry+file://[..]/alternative-registry)",
10841097
"license": null,
10851098
"license_file": null,
10861099
"description": null,
1087-
"source": null,
1088-
"dependencies": [
1089-
{
1090-
"name": "bar",
1091-
"source": "registry+https://github.com/rust-lang/crates.io-index",
1092-
"req": "^0.0.1",
1093-
"kind": null,
1094-
"rename": null,
1095-
"optional": false,
1096-
"uses_default_features": true,
1097-
"features": [],
1098-
"target": null,
1099-
"registry": null
1100-
}
1101-
],
1100+
"source": "registry+file://[..]/alternative-registry",
1101+
"dependencies": [],
11021102
"targets": "{...}",
11031103
"features": {},
1104-
"manifest_path": "[..]/foo/Cargo.toml",
1104+
"manifest_path": "[..]",
11051105
"metadata": null,
11061106
"publish": null,
11071107
"authors": [],
@@ -1113,30 +1113,30 @@ fn unknown_registry() {
11131113
"links": null
11141114
},
11151115
{
1116-
"name": "bar",
1116+
"name": "foo",
11171117
"version": "0.0.1",
1118-
"id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
1118+
"id": "foo 0.0.1 (path+file://[..]/foo)",
11191119
"license": null,
11201120
"license_file": null,
11211121
"description": null,
1122-
"source": "registry+https://github.com/rust-lang/crates.io-index",
1122+
"source": null,
11231123
"dependencies": [
11241124
{
1125-
"name": "baz",
1126-
"source": "registry+file://[..]/alternative-registry",
1125+
"name": "bar",
1126+
"source": "registry+https://github.com/rust-lang/crates.io-index",
11271127
"req": "^0.0.1",
11281128
"kind": null,
11291129
"rename": null,
11301130
"optional": false,
11311131
"uses_default_features": true,
11321132
"features": [],
11331133
"target": null,
1134-
"registry": "file:[..]/alternative-registry"
1134+
"registry": null
11351135
}
11361136
],
11371137
"targets": "{...}",
11381138
"features": {},
1139-
"manifest_path": "[..]",
1139+
"manifest_path": "[..]/foo/Cargo.toml",
11401140
"metadata": null,
11411141
"publish": null,
11421142
"authors": [],

0 commit comments

Comments
 (0)