summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/src/cargo/ops/tree/graph.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/src/cargo/ops/tree/graph.rs')
-rw-r--r--src/tools/cargo/src/cargo/ops/tree/graph.rs685
1 files changed, 685 insertions, 0 deletions
diff --git a/src/tools/cargo/src/cargo/ops/tree/graph.rs b/src/tools/cargo/src/cargo/ops/tree/graph.rs
new file mode 100644
index 000000000..20a9ca0b6
--- /dev/null
+++ b/src/tools/cargo/src/cargo/ops/tree/graph.rs
@@ -0,0 +1,685 @@
+//! 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;
+ }
+ 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 => {
+ log::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,
+ );
+ }
+ }
+ }
+ }
+ }
+}