Skip to content

Commit c46c06a

Browse files
committed
Auto merge of #8824 - ehuss:normalize-source-master, r=alexcrichton
Normalize SourceID in `cargo metadata`. The SourceID in `cargo metadata` can have different values, but they can be equivalent in Cargo. This results in different serialized forms, which prevents comparing the ID strings. In this particular case, `SourceKind::Git(GitReference::Branch("master"))` is equivalent to `SourceKind::Git(GitReference::DefaultBranch)`, but they serialize differently. The reason these end up differently is because the `SourceId` for a `Package` is created from the `Dependency` declaration. But the `SourceId` in `Cargo.lock` comes from the deserialized file. If you have an explicit `branch = "master"` in the dependency, then versions prior to 1.47 would *not* include `?branch=master` in `Cargo.lock`. However, since 1.47, internally Cargo will use `GitReference::Branch("master")`. Conversely, if you have a new `Cargo.lock` (with `?branch=master`), and then *remove* the explicit `branch="master"` from `Cargo.toml`, you'll end up with another mismatch in `cargo metadata`. The solution here is to use the variant from the `Package` when serializing the resolver in `cargo metadata`. I chose this since the `Package` variant is displayed on other JSON messages (like artifact messages), and I think this is the only place that the resolver variants are exposed (other than `Cargo.lock` itself). I'm not convinced that this was entirely intended, since there is [code to avoid this](https://github.com/rust-lang/cargo/blob/6a38927551df9bbe2dea340bf92d3e53abccf890/src/cargo/core/resolver/encode.rs#L688-L695), and at the time #8522 landed, I did not realize this would change the V2 lock format. However, it's probably too late to try to reverse that, and I don't think there are any other problems other than this `cargo metadata` inconsistency. Fixes #8756
2 parents 03137ed + d88ed1c commit c46c06a

File tree

2 files changed

+224
-3
lines changed

2 files changed

+224
-3
lines changed

src/cargo/ops/cargo_output_metadata.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,21 @@ fn build_resolve_graph_r(
180180
if node_map.contains_key(&pkg_id) {
181181
return;
182182
}
183+
// This normalizes the IDs so that they are consistent between the
184+
// `packages` array and the `resolve` map. This is a bit of a hack to
185+
// compensate for the fact that
186+
// SourceKind::Git(GitReference::Branch("master")) is the same as
187+
// SourceKind::Git(GitReference::DefaultBranch). We want IDs in the JSON
188+
// to be opaque, and compare with basic string equality, so this will
189+
// always prefer the style of ID in the Package instead of the resolver.
190+
// Cargo generally only exposes PackageIds from the Package struct, and
191+
// AFAIK this is the only place where the resolver variant is exposed.
192+
//
193+
// This diverges because the SourceIds created for Packages are built
194+
// based on the Dependency declaration, but the SourceIds in the resolver
195+
// are deserialized from Cargo.lock. Cargo.lock may have been generated by
196+
// an older (or newer!) version of Cargo which uses a different style.
197+
let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 };
183198
let features = resolve.features(pkg_id).to_vec();
184199

185200
let deps: Vec<Dep> = resolve
@@ -203,15 +218,15 @@ fn build_resolve_graph_r(
203218
.and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok())
204219
.map(|name| Dep {
205220
name,
206-
pkg: dep_id,
221+
pkg: normalize_id(dep_id),
207222
dep_kinds,
208223
})
209224
})
210225
.collect();
211-
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
226+
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| normalize_id(dep.pkg)).collect();
212227
let to_visit = dumb_deps.clone();
213228
let node = MetadataResolveNode {
214-
id: pkg_id,
229+
id: normalize_id(pkg_id),
215230
dependencies: dumb_deps,
216231
deps,
217232
features,

tests/testsuite/git.rs

+206
Original file line numberDiff line numberDiff line change
@@ -3020,3 +3020,209 @@ warning: [..]
30203020
)
30213021
.run();
30223022
}
3023+
3024+
#[cargo_test]
3025+
fn metadata_master_consistency() {
3026+
// SourceId consistency in the `cargo metadata` output when `master` is
3027+
// explicit or implicit, using new or old Cargo.lock.
3028+
let (git_project, git_repo) = git::new_repo("bar", |project| {
3029+
project
3030+
.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
3031+
.file("src/lib.rs", "")
3032+
});
3033+
let bar_hash = git_repo.head().unwrap().target().unwrap().to_string();
3034+
3035+
// Explicit branch="master" with a lock file created before 1.47 (does not contain ?branch=master).
3036+
let p = project()
3037+
.file(
3038+
"Cargo.toml",
3039+
&format!(
3040+
r#"
3041+
[package]
3042+
name = "foo"
3043+
version = "0.1.0"
3044+
3045+
[dependencies]
3046+
bar = {{ git = "{}", branch = "master" }}
3047+
"#,
3048+
git_project.url()
3049+
),
3050+
)
3051+
.file(
3052+
"Cargo.lock",
3053+
&format!(
3054+
r#"
3055+
[[package]]
3056+
name = "bar"
3057+
version = "1.0.0"
3058+
source = "git+{}#{}"
3059+
3060+
[[package]]
3061+
name = "foo"
3062+
version = "0.1.0"
3063+
dependencies = [
3064+
"bar",
3065+
]
3066+
"#,
3067+
git_project.url(),
3068+
bar_hash,
3069+
),
3070+
)
3071+
.file("src/lib.rs", "")
3072+
.build();
3073+
3074+
let metadata = |bar_source| -> String {
3075+
r#"
3076+
{
3077+
"packages": [
3078+
{
3079+
"name": "bar",
3080+
"version": "1.0.0",
3081+
"id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
3082+
"license": null,
3083+
"license_file": null,
3084+
"description": null,
3085+
"source": "__BAR_SOURCE__#__BAR_HASH__",
3086+
"dependencies": [],
3087+
"targets": "{...}",
3088+
"features": {},
3089+
"manifest_path": "[..]",
3090+
"metadata": null,
3091+
"publish": null,
3092+
"authors": [],
3093+
"categories": [],
3094+
"keywords": [],
3095+
"readme": null,
3096+
"repository": null,
3097+
"homepage": null,
3098+
"documentation": null,
3099+
"edition": "2015",
3100+
"links": null
3101+
},
3102+
{
3103+
"name": "foo",
3104+
"version": "0.1.0",
3105+
"id": "foo 0.1.0 [..]",
3106+
"license": null,
3107+
"license_file": null,
3108+
"description": null,
3109+
"source": null,
3110+
"dependencies": [
3111+
{
3112+
"name": "bar",
3113+
"source": "__BAR_SOURCE__",
3114+
"req": "*",
3115+
"kind": null,
3116+
"rename": null,
3117+
"optional": false,
3118+
"uses_default_features": true,
3119+
"features": [],
3120+
"target": null,
3121+
"registry": null
3122+
}
3123+
],
3124+
"targets": "{...}",
3125+
"features": {},
3126+
"manifest_path": "[..]",
3127+
"metadata": null,
3128+
"publish": null,
3129+
"authors": [],
3130+
"categories": [],
3131+
"keywords": [],
3132+
"readme": null,
3133+
"repository": null,
3134+
"homepage": null,
3135+
"documentation": null,
3136+
"edition": "2015",
3137+
"links": null
3138+
}
3139+
],
3140+
"workspace_members": [
3141+
"foo 0.1.0 [..]"
3142+
],
3143+
"resolve": {
3144+
"nodes": [
3145+
{
3146+
"id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
3147+
"dependencies": [],
3148+
"deps": [],
3149+
"features": []
3150+
},
3151+
{
3152+
"id": "foo 0.1.0 [..]",
3153+
"dependencies": [
3154+
"bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)"
3155+
],
3156+
"deps": [
3157+
{
3158+
"name": "bar",
3159+
"pkg": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
3160+
"dep_kinds": [
3161+
{
3162+
"kind": null,
3163+
"target": null
3164+
}
3165+
]
3166+
}
3167+
],
3168+
"features": []
3169+
}
3170+
],
3171+
"root": "foo 0.1.0 [..]"
3172+
},
3173+
"target_directory": "[..]",
3174+
"version": 1,
3175+
"workspace_root": "[..]",
3176+
"metadata": null
3177+
}
3178+
"#
3179+
.replace("__BAR_SOURCE__", bar_source)
3180+
.replace("__BAR_HASH__", &bar_hash)
3181+
};
3182+
3183+
let bar_source = format!("git+{}?branch=master", git_project.url());
3184+
p.cargo("metadata").with_json(&metadata(&bar_source)).run();
3185+
3186+
// Conversely, remove branch="master" from Cargo.toml, but use a new Cargo.lock that has ?branch=master.
3187+
let p = project()
3188+
.file(
3189+
"Cargo.toml",
3190+
&format!(
3191+
r#"
3192+
[package]
3193+
name = "foo"
3194+
version = "0.1.0"
3195+
3196+
[dependencies]
3197+
bar = {{ git = "{}" }}
3198+
"#,
3199+
git_project.url()
3200+
),
3201+
)
3202+
.file(
3203+
"Cargo.lock",
3204+
&format!(
3205+
r#"
3206+
[[package]]
3207+
name = "bar"
3208+
version = "1.0.0"
3209+
source = "git+{}?branch=master#{}"
3210+
3211+
[[package]]
3212+
name = "foo"
3213+
version = "0.1.0"
3214+
dependencies = [
3215+
"bar",
3216+
]
3217+
"#,
3218+
git_project.url(),
3219+
bar_hash
3220+
),
3221+
)
3222+
.file("src/lib.rs", "")
3223+
.build();
3224+
3225+
// No ?branch=master!
3226+
let bar_source = format!("git+{}", git_project.url());
3227+
p.cargo("metadata").with_json(&metadata(&bar_source)).run();
3228+
}

0 commit comments

Comments
 (0)