| //! Code for building the graph used by `cargo tree`. |
| |
| use super::TreeOptions; |
| use crate::core::compiler::{CompileKind, RustcTargetData}; |
| use crate::core::dependency::DepKind; |
| use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; |
| use crate::core::resolver::Resolve; |
| use crate::core::{FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace}; |
| use crate::util::interning::InternedString; |
| use crate::util::CargoResult; |
| use std::collections::{HashMap, HashSet}; |
| |
| #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub enum Node { |
| Package { |
| package_id: PackageId, |
| /// Features that are enabled on this package. |
| features: Vec<InternedString>, |
| kind: CompileKind, |
| }, |
| Feature { |
| /// Index of the package node this feature is for. |
| node_index: usize, |
| /// Name of the feature. |
| name: InternedString, |
| }, |
| } |
| |
| /// The kind of edge, for separating dependencies into different sections. |
| #[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)] |
| pub enum EdgeKind { |
| Dep(DepKind), |
| Feature, |
| } |
| |
| /// Set of outgoing edges for a single node. |
| /// |
| /// Edges are separated by the edge kind (`DepKind` or `Feature`). This is |
| /// primarily done so that the output can easily display separate sections |
| /// like `[build-dependencies]`. |
| /// |
| /// The value is a `Vec` because each edge kind can have multiple outgoing |
| /// edges. For example, package "foo" can have multiple normal dependencies. |
| #[derive(Clone)] |
| struct Edges(HashMap<EdgeKind, Vec<usize>>); |
| |
| impl Edges { |
| fn new() -> Edges { |
| Edges(HashMap::new()) |
| } |
| |
| /// Adds an edge pointing to the given node. |
| fn add_edge(&mut self, kind: EdgeKind, index: usize) { |
| let indexes = self.0.entry(kind).or_default(); |
| if !indexes.contains(&index) { |
| indexes.push(index) |
| } |
| } |
| } |
| |
| /// A graph of dependencies. |
| pub struct Graph<'a> { |
| nodes: Vec<Node>, |
| /// The indexes of `edges` correspond to the `nodes`. That is, `edges[0]` |
| /// is the set of outgoing edges for `nodes[0]`. They should always be in |
| /// sync. |
| edges: Vec<Edges>, |
| /// Index maps a node to an index, for fast lookup. |
| index: HashMap<Node, usize>, |
| /// Map for looking up packages. |
| package_map: HashMap<PackageId, &'a Package>, |
| /// Set of indexes of feature nodes that were added via the command-line. |
| /// |
| /// For example `--features foo` will mark the "foo" node here. |
| cli_features: HashSet<usize>, |
| /// Map of dependency names, used for building internal feature map for |
| /// dep_name/feat_name syntax. |
| /// |
| /// Key is the index of a package node, value is a map of dep_name to a |
| /// set of `(pkg_node_index, is_optional)`. |
| dep_name_map: HashMap<usize, HashMap<InternedString, HashSet<(usize, bool)>>>, |
| } |
| |
| impl<'a> Graph<'a> { |
| fn new(package_map: HashMap<PackageId, &'a Package>) -> Graph<'a> { |
| Graph { |
| nodes: Vec::new(), |
| edges: Vec::new(), |
| index: HashMap::new(), |
| package_map, |
| cli_features: HashSet::new(), |
| dep_name_map: HashMap::new(), |
| } |
| } |
| |
| /// Adds a new node to the graph, returning its new index. |
| fn add_node(&mut self, node: Node) -> usize { |
| let from_index = self.nodes.len(); |
| self.nodes.push(node); |
| self.edges.push(Edges::new()); |
| self.index |
| .insert(self.nodes[from_index].clone(), from_index); |
| from_index |
| } |
| |
| /// Returns a list of nodes the given node index points to for the given kind. |
| pub fn connected_nodes(&self, from: usize, kind: &EdgeKind) -> Vec<usize> { |
| match self.edges[from].0.get(kind) { |
| Some(indexes) => { |
| // Created a sorted list for consistent output. |
| let mut indexes = indexes.clone(); |
| indexes.sort_unstable_by(|a, b| self.nodes[*a].cmp(&self.nodes[*b])); |
| indexes |
| } |
| None => Vec::new(), |
| } |
| } |
| |
| /// Returns `true` if the given node has any outgoing edges. |
| pub fn has_outgoing_edges(&self, index: usize) -> bool { |
| !self.edges[index].0.is_empty() |
| } |
| |
| /// Gets a node by index. |
| pub fn node(&self, index: usize) -> &Node { |
| &self.nodes[index] |
| } |
| |
| /// Given a slice of PackageIds, returns the indexes of all nodes that match. |
| pub fn indexes_from_ids(&self, package_ids: &[PackageId]) -> Vec<usize> { |
| let mut result: Vec<(&Node, usize)> = self |
| .nodes |
| .iter() |
| .enumerate() |
| .filter(|(_i, node)| match node { |
| Node::Package { package_id, .. } => package_ids.contains(package_id), |
| _ => false, |
| }) |
| .map(|(i, node)| (node, i)) |
| .collect(); |
| // Sort for consistent output (the same command should always return |
| // the same output). "unstable" since nodes should always be unique. |
| result.sort_unstable(); |
| result.into_iter().map(|(_node, i)| i).collect() |
| } |
| |
| pub fn package_for_id(&self, id: PackageId) -> &Package { |
| self.package_map[&id] |
| } |
| |
| fn package_id_for_index(&self, index: usize) -> PackageId { |
| match self.nodes[index] { |
| Node::Package { package_id, .. } => package_id, |
| Node::Feature { .. } => panic!("unexpected feature node"), |
| } |
| } |
| |
| /// Returns `true` if the given feature node index is a feature enabled |
| /// via the command-line. |
| pub fn is_cli_feature(&self, index: usize) -> bool { |
| self.cli_features.contains(&index) |
| } |
| |
| /// Returns a new graph by removing all nodes not reachable from the |
| /// given nodes. |
| pub fn from_reachable(&self, roots: &[usize]) -> Graph<'a> { |
| // Graph built with features does not (yet) support --duplicates. |
| assert!(self.dep_name_map.is_empty()); |
| let mut new_graph = Graph::new(self.package_map.clone()); |
| // Maps old index to new index. None if not yet visited. |
| let mut remap: Vec<Option<usize>> = vec![None; self.nodes.len()]; |
| |
| fn visit( |
| graph: &Graph<'_>, |
| new_graph: &mut Graph<'_>, |
| remap: &mut Vec<Option<usize>>, |
| index: usize, |
| ) -> usize { |
| if let Some(new_index) = remap[index] { |
| // Already visited. |
| return new_index; |
| } |
| let node = graph.node(index).clone(); |
| let new_from = new_graph.add_node(node); |
| remap[index] = Some(new_from); |
| // Visit dependencies. |
| for (edge_kind, edge_indexes) in &graph.edges[index].0 { |
| for edge_index in edge_indexes { |
| let new_to_index = visit(graph, new_graph, remap, *edge_index); |
| new_graph.edges[new_from].add_edge(*edge_kind, new_to_index); |
| } |
| } |
| new_from |
| } |
| |
| // Walk the roots, generating a new graph as it goes along. |
| for root in roots { |
| visit(self, &mut new_graph, &mut remap, *root); |
| } |
| |
| new_graph |
| } |
| |
| /// Inverts the direction of all edges. |
| pub fn invert(&mut self) { |
| let mut new_edges = vec![Edges::new(); self.edges.len()]; |
| for (from_idx, node_edges) in self.edges.iter().enumerate() { |
| for (kind, edges) in &node_edges.0 { |
| for edge_idx in edges { |
| new_edges[*edge_idx].add_edge(*kind, from_idx); |
| } |
| } |
| } |
| self.edges = new_edges; |
| } |
| |
| /// Returns a list of nodes that are considered "duplicates" (same package |
| /// name, with different versions/features/source/etc.). |
| pub fn find_duplicates(&self) -> Vec<usize> { |
| // Graph built with features does not (yet) support --duplicates. |
| assert!(self.dep_name_map.is_empty()); |
| |
| // Collect a map of package name to Vec<(&Node, usize)>. |
| let mut packages = HashMap::new(); |
| for (i, node) in self.nodes.iter().enumerate() { |
| if let Node::Package { package_id, .. } = node { |
| packages |
| .entry(package_id.name()) |
| .or_insert_with(Vec::new) |
| .push((node, i)); |
| } |
| } |
| |
| let mut dupes: Vec<(&Node, usize)> = packages |
| .into_iter() |
| .filter(|(_name, indexes)| { |
| indexes |
| .into_iter() |
| .map(|(node, _)| { |
| match node { |
| Node::Package { |
| package_id, |
| features, |
| .. |
| } => { |
| // Do not treat duplicates on the host or target as duplicates. |
| Node::Package { |
| package_id: package_id.clone(), |
| features: features.clone(), |
| kind: CompileKind::Host, |
| } |
| } |
| _ => unreachable!(), |
| } |
| }) |
| .collect::<HashSet<_>>() |
| .len() |
| > 1 |
| }) |
| .flat_map(|(_name, indexes)| indexes) |
| .collect(); |
| |
| // For consistent output. |
| dupes.sort_unstable(); |
| dupes.into_iter().map(|(_node, i)| i).collect() |
| } |
| } |
| |
| /// Builds the graph. |
| pub fn build<'a>( |
| ws: &Workspace<'_>, |
| resolve: &Resolve, |
| resolved_features: &ResolvedFeatures, |
| specs: &[PackageIdSpec], |
| cli_features: &CliFeatures, |
| target_data: &RustcTargetData<'_>, |
| requested_kinds: &[CompileKind], |
| package_map: HashMap<PackageId, &'a Package>, |
| opts: &TreeOptions, |
| ) -> CargoResult<Graph<'a>> { |
| let mut graph = Graph::new(package_map); |
| let mut members_with_features = ws.members_with_features(specs, cli_features)?; |
| members_with_features.sort_unstable_by_key(|e| e.0.package_id()); |
| for (member, cli_features) in members_with_features { |
| let member_id = member.package_id(); |
| let features_for = FeaturesFor::from_for_host(member.proc_macro()); |
| for kind in requested_kinds { |
| let member_index = add_pkg( |
| &mut graph, |
| resolve, |
| resolved_features, |
| member_id, |
| features_for, |
| target_data, |
| *kind, |
| opts, |
| ); |
| if opts.graph_features { |
| let fmap = resolve.summary(member_id).features(); |
| add_cli_features(&mut graph, member_index, &cli_features, fmap); |
| } |
| } |
| } |
| if opts.graph_features { |
| add_internal_features(&mut graph, resolve); |
| } |
| Ok(graph) |
| } |
| |
| /// Adds a single package node (if it does not already exist). |
| /// |
| /// This will also recursively add all of its dependencies. |
| /// |
| /// Returns the index to the package node. |
| fn add_pkg( |
| graph: &mut Graph<'_>, |
| resolve: &Resolve, |
| resolved_features: &ResolvedFeatures, |
| package_id: PackageId, |
| features_for: FeaturesFor, |
| target_data: &RustcTargetData<'_>, |
| requested_kind: CompileKind, |
| opts: &TreeOptions, |
| ) -> usize { |
| let node_features = resolved_features.activated_features(package_id, features_for); |
| let node_kind = match features_for { |
| FeaturesFor::HostDep => CompileKind::Host, |
| FeaturesFor::ArtifactDep(target) => CompileKind::Target(target), |
| FeaturesFor::NormalOrDev => requested_kind, |
| }; |
| let node = Node::Package { |
| package_id, |
| features: node_features, |
| kind: node_kind, |
| }; |
| if let Some(idx) = graph.index.get(&node) { |
| return *idx; |
| } |
| let from_index = graph.add_node(node); |
| // Compute the dep name map which is later used for foo/bar feature lookups. |
| let mut dep_name_map: HashMap<InternedString, HashSet<(usize, bool)>> = HashMap::new(); |
| let mut deps: Vec<_> = resolve.deps(package_id).collect(); |
| deps.sort_unstable_by_key(|(dep_id, _)| *dep_id); |
| let show_all_targets = opts.target == super::Target::All; |
| for (dep_id, deps) in deps { |
| let mut deps: Vec<_> = deps |
| .iter() |
| // This filter is *similar* to the one found in `unit_dependencies::compute_deps`. |
| // Try to keep them in sync! |
| .filter(|dep| { |
| let kind = match (node_kind, dep.kind()) { |
| (CompileKind::Host, _) => CompileKind::Host, |
| (_, DepKind::Build) => CompileKind::Host, |
| (_, DepKind::Normal) => node_kind, |
| (_, DepKind::Development) => node_kind, |
| }; |
| // Filter out inactivated targets. |
| if !show_all_targets && !target_data.dep_platform_activated(dep, kind) { |
| return false; |
| } |
| // Filter out dev-dependencies if requested. |
| if !opts.edge_kinds.contains(&EdgeKind::Dep(dep.kind())) { |
| return false; |
| } |
| // Filter out proc-macrcos if requested. |
| if opts.no_proc_macro && graph.package_for_id(dep_id).proc_macro() { |
| return false; |
| } |
| if dep.is_optional() { |
| // If the new feature resolver does not enable this |
| // optional dep, then don't use it. |
| if !resolved_features.is_dep_activated( |
| package_id, |
| features_for, |
| dep.name_in_toml(), |
| ) { |
| return false; |
| } |
| } |
| true |
| }) |
| .collect(); |
| |
| // This dependency is eliminated from the dependency tree under |
| // the current target and feature set. |
| if deps.is_empty() { |
| continue; |
| } |
| |
| deps.sort_unstable_by_key(|dep| dep.name_in_toml()); |
| let dep_pkg = graph.package_map[&dep_id]; |
| |
| for dep in deps { |
| let dep_features_for = if dep.is_build() || dep_pkg.proc_macro() { |
| FeaturesFor::HostDep |
| } else { |
| features_for |
| }; |
| let dep_index = add_pkg( |
| graph, |
| resolve, |
| resolved_features, |
| dep_id, |
| dep_features_for, |
| target_data, |
| requested_kind, |
| opts, |
| ); |
| if opts.graph_features { |
| // Add the dependency node with feature nodes in-between. |
| dep_name_map |
| .entry(dep.name_in_toml()) |
| .or_default() |
| .insert((dep_index, dep.is_optional())); |
| if dep.uses_default_features() { |
| add_feature( |
| graph, |
| InternedString::new("default"), |
| Some(from_index), |
| dep_index, |
| EdgeKind::Dep(dep.kind()), |
| ); |
| } |
| for feature in dep.features().iter() { |
| add_feature( |
| graph, |
| *feature, |
| Some(from_index), |
| dep_index, |
| EdgeKind::Dep(dep.kind()), |
| ); |
| } |
| if !dep.uses_default_features() && dep.features().is_empty() { |
| // No features, use a direct connection. |
| graph.edges[from_index].add_edge(EdgeKind::Dep(dep.kind()), dep_index); |
| } |
| } else { |
| graph.edges[from_index].add_edge(EdgeKind::Dep(dep.kind()), dep_index); |
| } |
| } |
| } |
| if opts.graph_features { |
| assert!(graph |
| .dep_name_map |
| .insert(from_index, dep_name_map) |
| .is_none()); |
| } |
| |
| from_index |
| } |
| |
| /// Adds a feature node between two nodes. |
| /// |
| /// That is, it adds the following: |
| /// |
| /// ```text |
| /// from -Edge-> featname -Edge::Feature-> to |
| /// ``` |
| /// |
| /// Returns a tuple `(missing, index)`. |
| /// `missing` is true if this feature edge was already added. |
| /// `index` is the index of the index in the graph of the `Feature` node. |
| fn add_feature( |
| graph: &mut Graph<'_>, |
| name: InternedString, |
| from: Option<usize>, |
| to: usize, |
| kind: EdgeKind, |
| ) -> (bool, usize) { |
| // `to` *must* point to a package node. |
| assert!(matches! {graph.nodes[to], Node::Package{..}}); |
| let node = Node::Feature { |
| node_index: to, |
| name, |
| }; |
| let (missing, node_index) = match graph.index.get(&node) { |
| Some(idx) => (false, *idx), |
| None => (true, graph.add_node(node)), |
| }; |
| if let Some(from) = from { |
| graph.edges[from].add_edge(kind, node_index); |
| } |
| graph.edges[node_index].add_edge(EdgeKind::Feature, to); |
| (missing, node_index) |
| } |
| |
| /// Adds nodes for features requested on the command-line for the given member. |
| /// |
| /// Feature nodes are added as "roots" (i.e., they have no "from" index), |
| /// because they come from the outside world. They usually only appear with |
| /// `--invert`. |
| fn add_cli_features( |
| graph: &mut Graph<'_>, |
| package_index: usize, |
| cli_features: &CliFeatures, |
| feature_map: &FeatureMap, |
| ) { |
| // NOTE: Recursive enabling of features will be handled by |
| // add_internal_features. |
| |
| // Create a set of feature names requested on the command-line. |
| let mut to_add: HashSet<FeatureValue> = HashSet::new(); |
| if cli_features.all_features { |
| to_add.extend(feature_map.keys().map(|feat| FeatureValue::Feature(*feat))); |
| } |
| |
| if cli_features.uses_default_features { |
| to_add.insert(FeatureValue::Feature(InternedString::new("default"))); |
| } |
| to_add.extend(cli_features.features.iter().cloned()); |
| |
| // Add each feature as a node, and mark as "from command-line" in graph.cli_features. |
| for fv in to_add { |
| match fv { |
| FeatureValue::Feature(feature) => { |
| let index = add_feature(graph, feature, None, package_index, EdgeKind::Feature).1; |
| graph.cli_features.insert(index); |
| } |
| // This is enforced by CliFeatures. |
| FeatureValue::Dep { .. } => panic!("unexpected cli dep feature {}", fv), |
| FeatureValue::DepFeature { |
| dep_name, |
| dep_feature, |
| weak, |
| } => { |
| let dep_connections = match graph.dep_name_map[&package_index].get(&dep_name) { |
| // Clone to deal with immutable borrow of `graph`. :( |
| Some(dep_connections) => dep_connections.clone(), |
| None => { |
| // --features bar?/feat where `bar` is not activated should be ignored. |
| // If this wasn't weak, then this is a bug. |
| if weak { |
| continue; |
| } |
| panic!( |
| "missing dep graph connection for CLI feature `{}` for member {:?}\n\ |
| Please file a bug report at https://github.com/rust-lang/cargo/issues", |
| fv, |
| graph.nodes.get(package_index) |
| ); |
| } |
| }; |
| for (dep_index, is_optional) in dep_connections { |
| if is_optional { |
| // Activate the optional dep on self. |
| let index = |
| add_feature(graph, dep_name, None, package_index, EdgeKind::Feature).1; |
| graph.cli_features.insert(index); |
| } |
| let index = |
| add_feature(graph, dep_feature, None, dep_index, EdgeKind::Feature).1; |
| graph.cli_features.insert(index); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Recursively adds connections between features in the `[features]` table |
| /// for every package. |
| fn add_internal_features(graph: &mut Graph<'_>, resolve: &Resolve) { |
| // Collect features already activated by dependencies or command-line. |
| let feature_nodes: Vec<(PackageId, usize, usize, InternedString)> = graph |
| .nodes |
| .iter() |
| .enumerate() |
| .filter_map(|(i, node)| match node { |
| Node::Package { .. } => None, |
| Node::Feature { node_index, name } => { |
| let package_id = graph.package_id_for_index(*node_index); |
| Some((package_id, *node_index, i, *name)) |
| } |
| }) |
| .collect(); |
| |
| for (package_id, package_index, feature_index, feature_name) in feature_nodes { |
| add_feature_rec( |
| graph, |
| resolve, |
| feature_name, |
| package_id, |
| feature_index, |
| package_index, |
| ); |
| } |
| } |
| |
| /// Recursively add feature nodes for all features enabled by the given feature. |
| /// |
| /// `from` is the index of the node that enables this feature. |
| /// `package_index` is the index of the package node for the feature. |
| fn add_feature_rec( |
| graph: &mut Graph<'_>, |
| resolve: &Resolve, |
| feature_name: InternedString, |
| package_id: PackageId, |
| from: usize, |
| package_index: usize, |
| ) { |
| let feature_map = resolve.summary(package_id).features(); |
| let fvs = match feature_map.get(&feature_name) { |
| Some(fvs) => fvs, |
| None => return, |
| }; |
| for fv in fvs { |
| match fv { |
| FeatureValue::Feature(dep_name) => { |
| let (missing, feat_index) = add_feature( |
| graph, |
| *dep_name, |
| Some(from), |
| package_index, |
| EdgeKind::Feature, |
| ); |
| // Don't recursive if the edge already exists to deal with cycles. |
| if missing { |
| add_feature_rec( |
| graph, |
| resolve, |
| *dep_name, |
| package_id, |
| feat_index, |
| package_index, |
| ); |
| } |
| } |
| // Dependencies are already shown in the graph as dep edges. I'm |
| // uncertain whether or not this might be confusing in some cases |
| // (like feature `"somefeat" = ["dep:somedep"]`), so maybe in the |
| // future consider explicitly showing this? |
| FeatureValue::Dep { .. } => {} |
| FeatureValue::DepFeature { |
| dep_name, |
| dep_feature, |
| // Note: `weak` is mostly handled when the graph is built in |
| // `is_dep_activated` which is responsible for skipping |
| // unactivated weak dependencies. Here it is only used to |
| // determine if the feature of the dependency name is |
| // activated on self. |
| weak, |
| } => { |
| let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) { |
| Some(indexes) => indexes.clone(), |
| None => { |
| tracing::debug!( |
| "enabling feature {} on {}, found {}/{}, \ |
| dep appears to not be enabled", |
| feature_name, |
| package_id, |
| dep_name, |
| dep_feature |
| ); |
| continue; |
| } |
| }; |
| for (dep_index, is_optional) in dep_indexes { |
| let dep_pkg_id = graph.package_id_for_index(dep_index); |
| if is_optional && !weak { |
| // Activate the optional dep on self. |
| add_feature( |
| graph, |
| *dep_name, |
| Some(from), |
| package_index, |
| EdgeKind::Feature, |
| ); |
| } |
| let (missing, feat_index) = add_feature( |
| graph, |
| *dep_feature, |
| Some(from), |
| dep_index, |
| EdgeKind::Feature, |
| ); |
| if missing { |
| add_feature_rec( |
| graph, |
| resolve, |
| *dep_feature, |
| dep_pkg_id, |
| feat_index, |
| dep_index, |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |