Skip to content

Commit

Permalink
Rewrite target filtering to filter the entire graph.
Browse files Browse the repository at this point in the history
This also removes the special `host` target.
  • Loading branch information
ehuss committed Oct 28, 2019
1 parent 5b521f6 commit d6b5a6b
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 184 deletions.
222 changes: 112 additions & 110 deletions src/cargo/ops/cargo_output_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::core::compiler::{CompileKind, CompileTarget, TargetInfo};
use crate::core::resolver::{Resolve, ResolveOpts};
use crate::core::{Dependency, Package, PackageId, Workspace};
use crate::core::{Package, PackageId, Workspace};
use crate::ops::{self, Packages};
use crate::util::CargoResult;
use cargo_platform::Cfg;
use serde::ser;

use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
Expand Down Expand Up @@ -35,53 +34,14 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
let packages = ws.members().cloned().collect();
(packages, None)
} else {
let specs = Packages::All.to_package_id_specs(ws)?;
let opts = ResolveOpts::new(
let resolve_opts = ResolveOpts::new(
/*dev_deps*/ true,
&opt.features,
opt.all_features,
!opt.no_default_features,
);
let ws_resolve = ops::resolve_ws_with_opts(ws, opts, &specs)?;
let mut package_map = HashMap::new();
for pkg in ws_resolve
.pkg_set
.get_many(ws_resolve.pkg_set.package_ids())?
{
package_map.insert(pkg.package_id(), pkg.clone());
}
let packages = package_map.values().map(|p| (*p).clone()).collect();
let rustc = ws.config().load_global_rustc(Some(ws))?;
let (target, cfg) = match &opt.filter_platform {
Some(platform) => {
if platform == "host" {
let ti =
TargetInfo::new(ws.config(), CompileKind::Host, &rustc, CompileKind::Host)?;
(
Some(rustc.host.as_str().to_string()),
Some(ti.cfg().iter().cloned().collect()),
)
} else {
let kind = CompileKind::Target(CompileTarget::new(platform)?);
let ti = TargetInfo::new(ws.config(), kind, &rustc, kind)?;
(
Some(platform.clone()),
Some(ti.cfg().iter().cloned().collect()),
)
}
}
None => (None, None),
};
let resolve = Some(MetadataResolve {
helper: ResolveHelper {
packages: package_map,
resolve: ws_resolve.targeted_resolve,
target,
cfg,
},
root: ws.current_opt().map(|pkg| pkg.package_id()),
});
(packages, resolve)
let (packages, resolve) = build_resolve_graph(ws, resolve_opts, &opt.filter_platform)?;
(packages, Some(resolve))
};

Ok(ExportInfo {
Expand All @@ -104,81 +64,123 @@ pub struct ExportInfo {
workspace_root: PathBuf,
}

/// Newtype wrapper to provide a custom `Serialize` implementation.
/// The one from lock file does not fit because it uses a non-standard
/// format for `PackageId`s
#[derive(Serialize)]
struct MetadataResolve {
#[serde(rename = "nodes", serialize_with = "serialize_resolve")]
helper: ResolveHelper,
nodes: Vec<MetadataResolveNode>,
root: Option<PackageId>,
}

struct ResolveHelper {
packages: HashMap<PackageId, Package>,
resolve: Resolve,
target: Option<String>,
cfg: Option<Vec<Cfg>>,
#[derive(Serialize)]
struct MetadataResolveNode {
id: PackageId,
dependencies: Vec<PackageId>,
deps: Vec<Dep>,
features: Vec<String>,
}

fn serialize_resolve<S>(helper: &ResolveHelper, s: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let ResolveHelper {
packages,
resolve,
target,
cfg,
} = helper;
#[derive(Serialize)]
struct Dep {
name: String,
pkg: PackageId,
}

#[derive(Serialize)]
struct Dep {
name: String,
pkg: PackageId,
fn build_resolve_graph(
ws: &Workspace<'_>,
resolve_opts: ResolveOpts,
target: &Option<String>,
) -> CargoResult<(Vec<Package>, MetadataResolve)> {
let target_info = match target {
Some(target) => {
let config = ws.config();
let ct = CompileTarget::new(target)?;
let short_name = ct.short_name().to_string();
let kind = CompileKind::Target(ct);
let rustc = config.load_global_rustc(Some(ws))?;
Some((short_name, TargetInfo::new(config, kind, &rustc, kind)?))
}
None => None,
};
// Resolve entire workspace.
let specs = Packages::All.to_package_id_specs(ws)?;
let ws_resolve = ops::resolve_ws_with_opts(ws, resolve_opts, &specs)?;
// Download all Packages. This is needed to serialize the information
// for every package. In theory this could honor target filtering,
// but that would be somewhat complex.
let mut package_map: HashMap<PackageId, Package> = ws_resolve
.pkg_set
.get_many(ws_resolve.pkg_set.package_ids())?
.into_iter()
.map(|pkg| (pkg.package_id(), pkg.clone()))
.collect();
// Start from the workspace roots, and recurse through filling out the
// map, filtering targets as necessary.
let mut node_map = HashMap::new();
for member_pkg in ws.members() {
build_resolve_graph_r(
&mut node_map,
member_pkg.package_id(),
&ws_resolve.targeted_resolve,
&package_map,
target_info.as_ref(),
);
}
// Get a Vec of Packages.
let actual_packages = package_map
.drain()
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
.collect();
let mr = MetadataResolve {
nodes: node_map.drain().map(|(_pkg_id, node)| node).collect(),
root: ws.current_opt().map(|pkg| pkg.package_id()),
};
Ok((actual_packages, mr))
}

#[derive(Serialize)]
struct Node<'a> {
id: PackageId,
dependencies: Vec<PackageId>,
deps: Vec<Dep>,
features: Vec<&'a str>,
fn build_resolve_graph_r(
node_map: &mut HashMap<PackageId, MetadataResolveNode>,
pkg_id: PackageId,
resolve: &Resolve,
package_map: &HashMap<PackageId, Package>,
target: Option<&(String, TargetInfo)>,
) {
if node_map.contains_key(&pkg_id) {
return;
}

// A filter for removing platform dependencies.
let dep_filter = |(_pkg, deps): &(PackageId, &[Dependency])| match (target, cfg) {
(Some(target), Some(cfg)) => deps.iter().any(|dep| {
let platform = match dep.platform() {
Some(p) => p,
None => return true,
};
platform.matches(target, cfg)
}),
(None, None) => true,
_ => unreachable!(),
let features = resolve
.features_sorted(pkg_id)
.into_iter()
.map(|s| s.to_string())
.collect();
let deps: Vec<Dep> = resolve
.deps(pkg_id)
.filter(|(_dep_id, deps)| match target {
Some((short_name, info)) => deps.iter().any(|dep| {
let platform = match dep.platform() {
Some(p) => p,
None => return true,
};
platform.matches(short_name, info.cfg())
}),
None => true,
})
.filter_map(|(dep_id, _deps)| {
package_map
.get(&dep_id)
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok())
.map(|name| Dep { name, pkg: dep_id })
})
.collect();
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
let to_visit = dumb_deps.clone();
let node = MetadataResolveNode {
id: pkg_id,
dependencies: dumb_deps,
deps,
features,
};

s.collect_seq(resolve.iter().map(|id| {
Node {
id,
dependencies: resolve
.deps(id)
.filter(dep_filter)
.map(|(pkg, _deps)| pkg)
.collect(),
deps: resolve
.deps(id)
.filter(dep_filter)
.filter_map(|(pkg, _deps)| {
packages
.get(&pkg)
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| resolve.extern_crate_name(id, pkg, lib_target).ok())
.map(|name| Dep { name, pkg })
})
.collect(),
features: resolve.features_sorted(id),
}
}))
node_map.insert(pkg_id, node);
for dep_id in to_visit {
build_resolve_graph_r(node_map, dep_id, resolve, package_map, target);
}
}
Loading

0 comments on commit d6b5a6b

Please sign in to comment.