From d88ed1cb6cf7144e4734294cb2f74138eba34657 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sun, 1 Nov 2020 10:48:21 -0800 Subject: [PATCH] Normalize SourceID in `cargo metadata`. --- src/cargo/ops/cargo_output_metadata.rs | 21 ++- tests/testsuite/git.rs | 206 +++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 3 deletions(-) diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index 70709efb056..cf6fd629ed6 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -180,6 +180,21 @@ fn build_resolve_graph_r( if node_map.contains_key(&pkg_id) { return; } + // This normalizes the IDs so that they are consistent between the + // `packages` array and the `resolve` map. This is a bit of a hack to + // compensate for the fact that + // SourceKind::Git(GitReference::Branch("master")) is the same as + // SourceKind::Git(GitReference::DefaultBranch). We want IDs in the JSON + // to be opaque, and compare with basic string equality, so this will + // always prefer the style of ID in the Package instead of the resolver. + // Cargo generally only exposes PackageIds from the Package struct, and + // AFAIK this is the only place where the resolver variant is exposed. + // + // This diverges because the SourceIds created for Packages are built + // based on the Dependency declaration, but the SourceIds in the resolver + // are deserialized from Cargo.lock. Cargo.lock may have been generated by + // an older (or newer!) version of Cargo which uses a different style. + let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 }; let features = resolve.features(pkg_id).to_vec(); let deps: Vec = resolve @@ -203,15 +218,15 @@ fn build_resolve_graph_r( .and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok()) .map(|name| Dep { name, - pkg: dep_id, + pkg: normalize_id(dep_id), dep_kinds, }) }) .collect(); - let dumb_deps: Vec = deps.iter().map(|dep| dep.pkg).collect(); + let dumb_deps: Vec = deps.iter().map(|dep| normalize_id(dep.pkg)).collect(); let to_visit = dumb_deps.clone(); let node = MetadataResolveNode { - id: pkg_id, + id: normalize_id(pkg_id), dependencies: dumb_deps, deps, features, diff --git a/tests/testsuite/git.rs b/tests/testsuite/git.rs index 2337cd84983..548d7264c2a 100644 --- a/tests/testsuite/git.rs +++ b/tests/testsuite/git.rs @@ -3020,3 +3020,209 @@ warning: [..] ) .run(); } + +#[cargo_test] +fn metadata_master_consistency() { + // SourceId consistency in the `cargo metadata` output when `master` is + // explicit or implicit, using new or old Cargo.lock. + let (git_project, git_repo) = git::new_repo("bar", |project| { + project + .file("Cargo.toml", &basic_manifest("bar", "1.0.0")) + .file("src/lib.rs", "") + }); + let bar_hash = git_repo.head().unwrap().target().unwrap().to_string(); + + // Explicit branch="master" with a lock file created before 1.47 (does not contain ?branch=master). + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = {{ git = "{}", branch = "master" }} + "#, + git_project.url() + ), + ) + .file( + "Cargo.lock", + &format!( + r#" + [[package]] + name = "bar" + version = "1.0.0" + source = "git+{}#{}" + + [[package]] + name = "foo" + version = "0.1.0" + dependencies = [ + "bar", + ] + "#, + git_project.url(), + bar_hash, + ), + ) + .file("src/lib.rs", "") + .build(); + + let metadata = |bar_source| -> String { + r#" + { + "packages": [ + { + "name": "bar", + "version": "1.0.0", + "id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)", + "license": null, + "license_file": null, + "description": null, + "source": "__BAR_SOURCE__#__BAR_HASH__", + "dependencies": [], + "targets": "{...}", + "features": {}, + "manifest_path": "[..]", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null + }, + { + "name": "foo", + "version": "0.1.0", + "id": "foo 0.1.0 [..]", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "bar", + "source": "__BAR_SOURCE__", + "req": "*", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + } + ], + "targets": "{...}", + "features": {}, + "manifest_path": "[..]", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null + } + ], + "workspace_members": [ + "foo 0.1.0 [..]" + ], + "resolve": { + "nodes": [ + { + "id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "foo 0.1.0 [..]", + "dependencies": [ + "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)" + ], + "deps": [ + { + "name": "bar", + "pkg": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + } + ], + "root": "foo 0.1.0 [..]" + }, + "target_directory": "[..]", + "version": 1, + "workspace_root": "[..]", + "metadata": null + } + "# + .replace("__BAR_SOURCE__", bar_source) + .replace("__BAR_HASH__", &bar_hash) + }; + + let bar_source = format!("git+{}?branch=master", git_project.url()); + p.cargo("metadata").with_json(&metadata(&bar_source)).run(); + + // Conversely, remove branch="master" from Cargo.toml, but use a new Cargo.lock that has ?branch=master. + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = {{ git = "{}" }} + "#, + git_project.url() + ), + ) + .file( + "Cargo.lock", + &format!( + r#" + [[package]] + name = "bar" + version = "1.0.0" + source = "git+{}?branch=master#{}" + + [[package]] + name = "foo" + version = "0.1.0" + dependencies = [ + "bar", + ] + "#, + git_project.url(), + bar_hash + ), + ) + .file("src/lib.rs", "") + .build(); + + // No ?branch=master! + let bar_source = format!("git+{}", git_project.url()); + p.cargo("metadata").with_json(&metadata(&bar_source)).run(); +}