Add rename info to Cargo metadata

Previously, `cargo metadata` exposed dependencies info as a graph of
package id without any additional information on edges.

However, we do want to add some data to edges, including for example,
package renames and `cfg` info.

Internally, the edges info is represented as a `Vec<Dependnecy>`. We
could have exposed that directly, but that risks exposing and
ossifying an implementation details.

So instead we collapse a `Vec<Dependnecy>` to a single JSON object,
which at the moment contains `id` and `rename` info only. It should be
possible to add additional fields to that object backwards compatibly.
Such representation does not correspond directly to any internal Cargo
data structure, but hopefully it shouldn't be to hard to maintain.

This representation also does not quite correspond to the "real
world", where dependencies are between crate (cargo targets), and not
packages. However, because each dep edge is a JSON object, adding a
target filter should be possible, which would handle dev-, build-, and
potential future bin-specific dependencies backwards-compatibly.
This commit is contained in:
Aleksey Kladov 2018-08-07 12:37:29 +03:00
parent 2db5b45dcd
commit 39b1f752f6
2 changed files with 246 additions and 16 deletions

View file

@ -1,7 +1,7 @@
use serde::ser;
use core::resolver::Resolve;
use core::{Package, PackageId, Workspace};
use core::{Package, PackageId, Workspace, PackageSet};
use ops::{self, Packages};
use util::CargoResult;
@ -18,7 +18,7 @@ pub struct OutputMetadataOptions {
/// Loads the manifest, resolves the dependencies of the project to the concrete
/// used versions - considering overrides - and writes all dependencies in a JSON
/// format to stdout.
pub fn output_metadata(ws: &Workspace, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
pub fn output_metadata<'a>(ws: &'a Workspace, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo<'a>> {
if opt.version != VERSION {
bail!(
"metadata version {} not supported, only {} is currently supported",
@ -33,7 +33,7 @@ pub fn output_metadata(ws: &Workspace, opt: &OutputMetadataOptions) -> CargoResu
}
}
fn metadata_no_deps(ws: &Workspace, _opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
fn metadata_no_deps<'a>(ws: &'a Workspace, _opt: &OutputMetadataOptions) -> CargoResult<ExportInfo<'a>> {
Ok(ExportInfo {
packages: ws.members().cloned().collect(),
workspace_members: ws.members().map(|pkg| pkg.package_id().clone()).collect(),
@ -44,7 +44,7 @@ fn metadata_no_deps(ws: &Workspace, _opt: &OutputMetadataOptions) -> CargoResult
})
}
fn metadata_full(ws: &Workspace, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
fn metadata_full<'a>(ws: &'a Workspace, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo<'a>> {
let specs = Packages::All.to_package_id_specs(ws)?;
let deps = ops::resolve_ws_precisely(
ws,
@ -54,18 +54,18 @@ fn metadata_full(ws: &Workspace, opt: &OutputMetadataOptions) -> CargoResult<Exp
opt.no_default_features,
&specs,
)?;
let (packages, resolve) = deps;
let (package_set, resolve) = deps;
let packages = packages
let packages = package_set
.package_ids()
.map(|i| packages.get(i).map(|p| p.clone()))
.map(|i| package_set.get(i).map(|p| p.clone()))
.collect::<CargoResult<Vec<_>>>()?;
Ok(ExportInfo {
packages,
workspace_members: ws.members().map(|pkg| pkg.package_id().clone()).collect(),
resolve: Some(MetadataResolve {
resolve,
resolve: (package_set, resolve),
root: ws.current_opt().map(|pkg| pkg.package_id().clone()),
}),
target_directory: ws.target_dir().display().to_string(),
@ -75,10 +75,10 @@ fn metadata_full(ws: &Workspace, opt: &OutputMetadataOptions) -> CargoResult<Exp
}
#[derive(Serialize)]
pub struct ExportInfo {
pub struct ExportInfo<'a> {
packages: Vec<Package>,
workspace_members: Vec<PackageId>,
resolve: Option<MetadataResolve>,
resolve: Option<MetadataResolve<'a>>,
target_directory: String,
version: u32,
workspace_root: String,
@ -88,20 +88,27 @@ pub struct ExportInfo {
/// The one from lockfile does not fit because it uses a non-standard
/// format for `PackageId`s
#[derive(Serialize)]
struct MetadataResolve {
struct MetadataResolve<'a> {
#[serde(rename = "nodes", serialize_with = "serialize_resolve")]
resolve: Resolve,
resolve: (PackageSet<'a>, Resolve),
root: Option<PackageId>,
}
fn serialize_resolve<S>(resolve: &Resolve, s: S) -> Result<S::Ok, S::Error>
fn serialize_resolve<S>((package_set, resolve): &(PackageSet, Resolve), s: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
#[derive(Serialize)]
struct Dep<'a> {
name: Option<String>,
pkg: &'a PackageId
}
#[derive(Serialize)]
struct Node<'a> {
id: &'a PackageId,
dependencies: Vec<&'a PackageId>,
deps: Vec<Dep<'a>>,
features: Vec<&'a str>,
}
@ -109,7 +116,18 @@ where
.iter()
.map(|id| Node {
id,
dependencies: resolve.deps(id).map(|p| p.0).collect(),
dependencies: resolve.deps(id).map(|(pkg, _deps)| pkg).collect(),
deps: resolve.deps(id)
.map(|(pkg, _deps)| {
let name = package_set.get(pkg).ok()
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| {
resolve.extern_crate_name(id, pkg, lib_target).ok()
});
Dep { name, pkg }
})
.collect(),
features: resolve.features_sorted(id),
}))
}

View file

@ -55,6 +55,7 @@ fn cargo_metadata_simple() {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "foo 0.5.0 (path+file:[..]foo)"
}
@ -148,6 +149,7 @@ crate-type = ["lib", "staticlib"]
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "foo 0.5.0 (path+file:[..]foo)"
}
@ -231,6 +233,7 @@ optional_feat = []
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [
"default",
"default_feat"
@ -411,6 +414,9 @@ fn cargo_metadata_with_deps_and_version() {
"dependencies": [
"bar 0.0.1 (registry+[..])"
],
"deps": [
{ "name": "bar", "pkg": "bar 0.0.1 (registry+[..])" }
],
"features": [],
"id": "foo 0.5.0 (path+file:[..]foo)"
},
@ -418,11 +424,15 @@ fn cargo_metadata_with_deps_and_version() {
"dependencies": [
"baz 0.0.1 (registry+[..])"
],
"deps": [
{ "name": "baz", "pkg": "baz 0.0.1 (registry+[..])" }
],
"features": [],
"id": "bar 0.0.1 (registry+[..])"
},
{
"dependencies": [],
"deps": [],
"features": [],
"id": "baz 0.0.1 (registry+[..])"
}
@ -506,7 +516,8 @@ name = "ex"
{
"id": "foo 0.1.0 (path+file:[..]foo)",
"features": [],
"dependencies": []
"dependencies": [],
"deps": []
}
]
},
@ -588,7 +599,8 @@ crate-type = ["rlib", "dylib"]
{
"id": "foo 0.1.0 (path+file:[..]foo)",
"features": [],
"dependencies": []
"dependencies": [],
"deps": []
}
]
},
@ -688,11 +700,13 @@ fn workspace_metadata() {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "baz 0.5.0 (path+file:[..]baz)"
},
{
"dependencies": [],
"deps": [],
"features": [],
"id": "bar 0.5.0 (path+file:[..]bar)"
}
@ -1127,6 +1141,7 @@ fn cargo_metadata_path_to_cargo_toml_project() {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "bar 0.5.0 ([..])"
}
@ -1207,6 +1222,7 @@ fn package_edition_2018() {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "foo 0.1.0 (path+file:[..])"
}
@ -1302,6 +1318,7 @@ fn target_edition_2018() {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "foo 0.1.0 (path+file:[..])"
}
@ -1319,3 +1336,198 @@ fn target_edition_2018() {
),
);
}
#[test]
fn rename_dependency() {
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.2.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["rename-dependency"]
[project]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar = { version = "0.1.0" }
baz = { version = "0.2.0", package = "bar" }
"#,
)
.file("src/lib.rs", "extern crate bar; extern crate baz;")
.build();
assert_that(
p.cargo("metadata").masquerade_as_nightly_cargo(),
execs().with_json(r#"
{
"packages": [
{
"authors": [],
"categories": [],
"dependencies": [
{
"features": [],
"kind": null,
"name": "bar",
"optional": false,
"rename": null,
"req": "^0.1.0",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
},
{
"features": [],
"kind": null,
"name": "bar",
"optional": false,
"rename": "baz",
"req": "^0.2.0",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
}
],
"description": null,
"edition": "2015",
"features": {},
"id": "foo 0.0.1[..]",
"keywords": [],
"license": null,
"license_file": null,
"manifest_path": "[..]",
"metadata": null,
"name": "foo",
"readme": null,
"repository": null,
"source": null,
"targets": [
{
"crate_types": [
"lib"
],
"edition": "2015",
"kind": [
"lib"
],
"name": "foo",
"src_path": "[..]"
}
],
"version": "0.0.1"
},
{
"authors": [],
"categories": [],
"dependencies": [],
"description": null,
"edition": "2015",
"features": {},
"id": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keywords": [],
"license": null,
"license_file": null,
"manifest_path": "[..]",
"metadata": null,
"name": "bar",
"readme": null,
"repository": null,
"source": "registry+https://github.com/rust-lang/crates.io-index",
"targets": [
{
"crate_types": [
"lib"
],
"edition": "2015",
"kind": [
"lib"
],
"name": "bar",
"src_path": "[..]"
}
],
"version": "0.1.0"
},
{
"authors": [],
"categories": [],
"dependencies": [],
"description": null,
"edition": "2015",
"features": {},
"id": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keywords": [],
"license": null,
"license_file": null,
"manifest_path": "[..]",
"metadata": null,
"name": "bar",
"readme": null,
"repository": null,
"source": "registry+https://github.com/rust-lang/crates.io-index",
"targets": [
{
"crate_types": [
"lib"
],
"edition": "2015",
"kind": [
"lib"
],
"name": "bar",
"src_path": "[..]"
}
],
"version": "0.2.0"
}
],
"resolve": {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
},
{
"dependencies": [],
"deps": [],
"features": [],
"id": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)"
},
{
"dependencies": [
"bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
],
"deps": [
{
"name": "bar",
"pkg": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)"
},
{
"name": "baz",
"pkg": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
}
],
"features": [],
"id": "foo 0.0.1[..]"
}
],
"root": "foo 0.0.1[..]"
},
"target_directory": "[..]",
"version": 1,
"workspace_members": [
"foo 0.0.1[..]"
],
"workspace_root": "[..]"
}"#,
),
);
}