| //! Code for building the graph used by `cargo tree`. |
| |
| use super::TreeOptions; |
| use crate::compiler::{CompileKind, RustcTargetData}; |
| use crate::resolver::Resolve; |
| use crate::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; |
| use crate::util::CargoResult; |
| use crate::util::data_structures::{HashMap, HashSet}; |
| use crate::util::interning::{INTERNED_DEFAULT, InternedString}; |
| use crate::workspace::dependency::DepKind; |
| use crate::workspace::{FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace}; |
| |
| #[derive(Debug, Copy, Clone)] |
| pub struct NodeId { |
| index: usize, |
| #[expect(dead_code, reason = "intended for `derive(Debug)`")] |
| debug: InternedString, |
| } |
| |
| impl NodeId { |
| fn new(index: usize, debug: InternedString) -> Self { |
| Self { index, debug } |
| } |
| } |
| |
| impl PartialEq for NodeId { |
| fn eq(&self, other: &Self) -> bool { |
| self.index == other.index |
| } |
| } |
| |
| impl Eq for NodeId {} |
| |
| impl PartialOrd for NodeId { |
| fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| Some(self.cmp(other)) |
| } |
| } |
| |
| impl Ord for NodeId { |
| fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| self.index.cmp(&other.index) |
| } |
| } |
| |
| impl std::hash::Hash for NodeId { |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| self.index.hash(state) |
| } |
| } |
| |
| #[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: NodeId, |
| /// Name of the feature. |
| name: InternedString, |
| }, |
| } |
| |
| impl Node { |
| fn name(&self) -> InternedString { |
| match self { |
| Self::Package { package_id, .. } => package_id.name(), |
| Self::Feature { name, .. } => *name, |
| } |
| } |
| } |
| |
| #[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)] |
| pub struct Edge { |
| kind: EdgeKind, |
| node: NodeId, |
| public: bool, |
| } |
| |
| impl Edge { |
| pub fn kind(&self) -> EdgeKind { |
| self.kind |
| } |
| |
| pub fn node(&self) -> NodeId { |
| self.node |
| } |
| |
| pub fn public(&self) -> bool { |
| self.public |
| } |
| } |
| |
| /// 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, Debug)] |
| struct Edges(HashMap<EdgeKind, Vec<Edge>>); |
| |
| impl Edges { |
| fn new() -> Edges { |
| Edges(HashMap::default()) |
| } |
| |
| /// Adds an edge pointing to the given node. |
| fn add_edge(&mut self, edge: Edge) { |
| let indexes = self.0.entry(edge.kind()).or_default(); |
| if !indexes.contains(&edge) { |
| indexes.push(edge) |
| } |
| } |
| |
| fn all(&self) -> impl Iterator<Item = &Edge> + '_ { |
| self.0.values().flatten() |
| } |
| |
| fn of_kind(&self, kind: &EdgeKind) -> &[Edge] { |
| self.0.get(kind).map(Vec::as_slice).unwrap_or_default() |
| } |
| |
| fn is_empty(&self) -> bool { |
| self.0.is_empty() |
| } |
| } |
| |
| /// A graph of dependencies. |
| #[derive(Debug)] |
| 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, NodeId>, |
| /// 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<NodeId>, |
| /// 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<NodeId, HashMap<InternedString, HashSet<(NodeId, bool)>>>, |
| } |
| |
| impl<'a> Graph<'a> { |
| fn new(package_map: HashMap<PackageId, &'a Package>) -> Graph<'a> { |
| Graph { |
| nodes: Vec::new(), |
| edges: Vec::new(), |
| index: HashMap::default(), |
| package_map, |
| cli_features: HashSet::default(), |
| dep_name_map: HashMap::default(), |
| } |
| } |
| |
| /// Adds a new node to the graph, returning its new index. |
| fn add_node(&mut self, node: Node) -> NodeId { |
| let from_index = NodeId::new(self.nodes.len(), node.name()); |
| self.nodes.push(node); |
| self.edges.push(Edges::new()); |
| self.index.insert(self.node(from_index).clone(), from_index); |
| from_index |
| } |
| |
| /// Returns a list of nodes the given node index points to for the given kind. |
| pub fn edges_of_kind(&self, from: NodeId, kind: &EdgeKind) -> Vec<Edge> { |
| let edges = self.edges(from).of_kind(kind); |
| // Created a sorted list for consistent output. |
| let mut edges = edges.to_owned(); |
| edges.sort_unstable_by(|a, b| self.node(a.node()).cmp(&self.node(b.node()))); |
| edges |
| } |
| |
| fn edges(&self, from: NodeId) -> &Edges { |
| &self.edges[from.index] |
| } |
| |
| fn edges_mut(&mut self, from: NodeId) -> &mut Edges { |
| &mut self.edges[from.index] |
| } |
| |
| /// Returns `true` if the given node has any outgoing edges. |
| pub fn has_outgoing_edges(&self, index: NodeId) -> bool { |
| !self.edges(index).is_empty() |
| } |
| |
| /// Gets a node by index. |
| pub fn node(&self, index: NodeId) -> &Node { |
| &self.nodes[index.index] |
| } |
| |
| /// Given a slice of `PackageIds`, returns the indexes of all nodes that match. |
| pub fn indexes_from_ids(&self, package_ids: &[PackageId]) -> Vec<NodeId> { |
| let mut result: Vec<(&Node, NodeId)> = self |
| .nodes |
| .iter() |
| .enumerate() |
| .filter(|(_i, node)| match node { |
| Node::Package { package_id, .. } => package_ids.contains(package_id), |
| _ => false, |
| }) |
| .map(|(i, node)| (node, NodeId::new(i, node.name()))) |
| .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: NodeId) -> PackageId { |
| match self.node(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: NodeId) -> 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: &[NodeId]) -> 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<NodeId>> = vec![None; self.nodes.len()]; |
| |
| fn visit( |
| graph: &Graph<'_>, |
| new_graph: &mut Graph<'_>, |
| remap: &mut Vec<Option<NodeId>>, |
| index: NodeId, |
| ) -> NodeId { |
| if let Some(new_index) = remap[index.index] { |
| // Already visited. |
| return new_index; |
| } |
| let node = graph.node(index).clone(); |
| let new_from = new_graph.add_node(node); |
| remap[index.index] = Some(new_from); |
| // Visit dependencies. |
| for edge in graph.edges(index).all() { |
| let new_to_index = visit(graph, new_graph, remap, edge.node()); |
| let new_edge = Edge { |
| kind: edge.kind(), |
| node: new_to_index, |
| public: edge.public(), |
| }; |
| new_graph.edges_mut(new_from).add_edge(new_edge); |
| } |
| 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 edge in node_edges.all() { |
| let new_edge = Edge { |
| kind: edge.kind(), |
| node: NodeId::new(from_idx, self.nodes[from_idx].name()), |
| public: edge.public(), |
| }; |
| new_edges[edge.node().index].add_edge(new_edge); |
| } |
| } |
| 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<NodeId> { |
| // 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, NodeId)>. |
| let mut packages = HashMap::default(); |
| 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, NodeId::new(i, node.name()))); |
| } |
| } |
| |
| let mut dupes: Vec<(&Node, NodeId)> = 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(|(member, _)| member.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, |
| ) -> NodeId { |
| 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<(NodeId, bool)>> = HashMap::default(); |
| 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-macros if requested. |
| if opts.no_proc_macro && graph.package_for_id(dep_id).proc_macro() { |
| return false; |
| } |
| // Filter out private dependencies if requested. |
| if opts.public && !dep.is_public() { |
| 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.kind(), dep.name_in_toml())); |
| let dep_pkg = graph.package_map[&dep_id]; |
| |
| for dep in deps { |
| let dep_features_for = match dep |
| .artifact() |
| .and_then(|artifact| artifact.target()) |
| .and_then(|target| target.to_resolved_compile_target(requested_kind)) |
| { |
| // Dependency has a `{ …, target = <triple> }` |
| Some(target) => FeaturesFor::ArtifactDep(target), |
| // Get the information of the dependent crate from `features_for`. |
| // If a dependent crate is |
| // |
| // * specified as an artifact dep with a `target`, or |
| // * a host dep, |
| // |
| // its transitive deps, including build-deps, need to be built on that target. |
| None if features_for != FeaturesFor::default() => features_for, |
| // Dependent crate is a normal dep, then back to old rules: |
| // |
| // * normal deps, dev-deps -> inherited target |
| // * build-deps -> host |
| None => { |
| 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, |
| ); |
| let new_edge = Edge { |
| kind: EdgeKind::Dep(dep.kind()), |
| node: dep_index, |
| public: dep.is_public(), |
| }; |
| 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, INTERNED_DEFAULT, Some(from_index), new_edge); |
| } |
| for feature in dep.features().iter() { |
| add_feature(graph, *feature, Some(from_index), new_edge); |
| } |
| if !dep.uses_default_features() && dep.features().is_empty() { |
| // No features, use a direct connection. |
| graph.edges_mut(from_index).add_edge(new_edge); |
| } |
| } else { |
| graph.edges_mut(from_index).add_edge(new_edge); |
| } |
| } |
| } |
| 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<NodeId>, |
| to: Edge, |
| ) -> (bool, NodeId) { |
| // `to` *must* point to a package node. |
| assert!(matches! {graph.node(to.node()), Node::Package{..}}); |
| let node = Node::Feature { |
| node_index: to.node(), |
| 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 { |
| let from_edge = Edge { |
| kind: to.kind(), |
| node: node_index, |
| public: to.public(), |
| }; |
| graph.edges_mut(from).add_edge(from_edge); |
| } |
| let to_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: to.node(), |
| public: true, |
| }; |
| graph.edges_mut(node_index).add_edge(to_edge); |
| (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: NodeId, |
| 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::default(); |
| 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(INTERNED_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 feature_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: package_index, |
| public: true, |
| }; |
| let index = add_feature(graph, feature, None, feature_edge).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.index) |
| ); |
| } |
| }; |
| for (dep_index, is_optional) in dep_connections { |
| if is_optional { |
| // Activate the optional dep on self. |
| let feature_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: package_index, |
| public: true, |
| }; |
| let index = add_feature(graph, dep_name, None, feature_edge).1; |
| graph.cli_features.insert(index); |
| } |
| let dep_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: dep_index, |
| public: true, |
| }; |
| let index = add_feature(graph, dep_feature, None, dep_edge).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, NodeId, NodeId, 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, NodeId::new(i, *name), *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: NodeId, |
| package_index: NodeId, |
| ) { |
| let feature_map = resolve.summary(package_id).features(); |
| let Some(fvs) = feature_map.get(&feature_name) else { |
| return; |
| }; |
| for fv in fvs { |
| match fv { |
| FeatureValue::Feature(dep_name) => { |
| let feature_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: package_index, |
| public: true, |
| }; |
| let (missing, feat_index) = add_feature(graph, *dep_name, Some(from), feature_edge); |
| // 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. |
| let feature_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: package_index, |
| public: true, |
| }; |
| add_feature(graph, *dep_name, Some(from), feature_edge); |
| } |
| let dep_edge = Edge { |
| kind: EdgeKind::Feature, |
| node: dep_index, |
| public: true, |
| }; |
| let (missing, feat_index) = |
| add_feature(graph, *dep_feature, Some(from), dep_edge); |
| if missing { |
| add_feature_rec( |
| graph, |
| resolve, |
| *dep_feature, |
| dep_pkg_id, |
| feat_index, |
| dep_index, |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |