guppy/graph/feature/
graph_impl.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    debug_ignore::DebugIgnore,
6    errors::FeatureGraphWarning,
7    graph::{
8        feature::{
9            build::{FeatureGraphBuildState, FeaturePetgraph},
10            Cycles, FeatureFilter, FeatureList, WeakDependencies, WeakIndex,
11        },
12        DependencyDirection, FeatureIndexInPackage, FeatureIx, PackageGraph, PackageIx,
13        PackageLink, PackageMetadata,
14    },
15    petgraph_support::{scc::Sccs, topo::TopoWithCycles},
16    platform::{PlatformStatus, PlatformStatusImpl},
17    DependencyKind, Error, PackageId,
18};
19use ahash::AHashMap;
20use once_cell::sync::OnceCell;
21use petgraph::{
22    algo::has_path_connecting,
23    prelude::*,
24    visit::{EdgeFiltered, IntoNodeReferences},
25};
26use std::{fmt, iter, iter::FromIterator};
27
28// Some general notes about feature graphs:
29//
30// The set of features for a package is the named features (in the [features] section), plus any
31// optional dependencies.
32//
33// An optional dependency can be either normal or build -- not dev. Note that a dependency can be
34// marked optional in one section and required in another. In this context, a dependency is a
35// feature if it is marked as optional in any context.
36//
37// Features are *unified*. See the documentation in add_dependency_edges for more.
38//
39// There are a few ways features can be enabled. The most common is within a dependency spec. A
40// feature can also be specified via the command-line. Finally, named features can specify what
41// features a package depends on:
42//
43// ```toml
44// [features]
45// foo = ["a/bar", "optional-dep", "baz"]
46// baz = []
47// ```
48//
49// Feature names are unique. A named feature and an optional dep cannot have the same names.
50
51impl PackageGraph {
52    /// Returns a derived graph representing every feature of every package.
53    ///
54    /// The feature graph is constructed the first time this method is called. The graph is cached
55    /// so that repeated calls to this method are cheap.
56    pub fn feature_graph(&self) -> FeatureGraph {
57        let inner = self.get_feature_graph();
58        FeatureGraph {
59            package_graph: self,
60            inner,
61        }
62    }
63
64    pub(super) fn get_feature_graph(&self) -> &FeatureGraphImpl {
65        self.feature_graph
66            .get_or_init(|| FeatureGraphImpl::new(self))
67    }
68}
69
70/// A derived graph representing every feature of every package.
71///
72/// Constructed through `PackageGraph::feature_graph`.
73#[derive(Clone, Copy, Debug)]
74pub struct FeatureGraph<'g> {
75    pub(crate) package_graph: &'g PackageGraph,
76    pub(super) inner: &'g FeatureGraphImpl,
77}
78
79assert_covariant!(FeatureGraph);
80
81impl<'g> FeatureGraph<'g> {
82    /// Returns any non-fatal warnings encountered while constructing the feature graph.
83    pub fn build_warnings(&self) -> &'g [FeatureGraphWarning] {
84        &self.inner.warnings
85    }
86
87    /// Returns the `PackageGraph` from which this feature graph was constructed.
88    pub fn package_graph(&self) -> &'g PackageGraph {
89        self.package_graph
90    }
91
92    /// Returns the total number of (package ID, feature) combinations in this graph.
93    ///
94    /// Includes the "base" feature for each package.
95    pub fn feature_count(&self) -> usize {
96        self.dep_graph().node_count()
97    }
98
99    /// Returns the number of links in this graph.
100    pub fn link_count(&self) -> usize {
101        self.dep_graph().edge_count()
102    }
103
104    /// Returns true if this feature graph contains the specified feature.
105    pub fn contains(&self, feature_id: impl Into<FeatureId<'g>>) -> bool {
106        let feature_id = feature_id.into();
107        FeatureNode::from_id(self, feature_id).is_some()
108    }
109
110    /// Returns metadata for the given feature ID, or `None` if the feature wasn't found.
111    pub fn metadata(
112        &self,
113        feature_id: impl Into<FeatureId<'g>>,
114    ) -> Result<FeatureMetadata<'g>, Error> {
115        let feature_id = feature_id.into();
116        let feature_node = FeatureNode::from_id(self, feature_id)
117            .ok_or_else(|| Error::unknown_feature_id(feature_id))?;
118        self.metadata_for_node(feature_node)
119            .ok_or_else(|| Error::unknown_feature_id(feature_id))
120    }
121
122    /// Returns all known features for a package.
123    ///
124    /// Returns an error if the package ID was unknown.
125    pub fn all_features_for(&self, package_id: &PackageId) -> Result<FeatureList<'g>, Error> {
126        let package = self.package_graph.metadata(package_id)?;
127        let dep_graph = self.dep_graph();
128        let features = self
129            .feature_ixs_for_package_ix(package.package_ix())
130            .map(|feature_ix| FeatureId::node_to_feature(package, &dep_graph[feature_ix]));
131        Ok(FeatureList::new(package, features))
132    }
133
134    /// Returns true if this feature is included in a package's build by default.
135    ///
136    /// Returns an error if this feature ID is unknown.
137    ///
138    /// ## Cycles
139    ///
140    /// A cyclic dev-dependency may cause additional features to be turned on. This computation
141    /// does *not* follow conditional links and will *not* return true for such additional
142    /// features.
143    pub fn is_default_feature<'a>(
144        &self,
145        feature_id: impl Into<FeatureId<'a>>,
146    ) -> Result<bool, Error> {
147        let feature_id = feature_id.into();
148        let default_ix = self.feature_ix(
149            self.package_graph
150                .metadata(feature_id.package_id())?
151                .default_feature_id(),
152        )?;
153        let feature_ix = self.feature_ix(feature_id)?;
154        // Do not follow conditional links.
155        Ok(self.feature_ix_depends_on_no_conditional(default_ix, feature_ix))
156    }
157
158    /// Returns true if `feature_a` depends (directly or indirectly) on `feature_b`.
159    ///
160    /// In other words, this returns true if `feature_b` is a (possibly transitive) dependency of
161    /// `feature_a`.
162    ///
163    /// This also returns true if `feature_a` is the same as `feature_b`.
164    ///
165    /// Note that this returns true if `feature_a` [conditionally depends on][ConditionalLink] `feature_b`.
166    pub fn depends_on<'a>(
167        &self,
168        feature_a: impl Into<FeatureId<'a>>,
169        feature_b: impl Into<FeatureId<'a>>,
170    ) -> Result<bool, Error> {
171        let feature_a = feature_a.into();
172        let feature_b = feature_b.into();
173        let a_ix = self.feature_ix(feature_a)?;
174        let b_ix = self.feature_ix(feature_b)?;
175        Ok(self.feature_ix_depends_on(a_ix, b_ix))
176    }
177
178    /// Returns true if `feature_a` directly depends on `feature_b`.
179    ///
180    /// In other words, this returns true if `feature_a` is a direct dependency of `feature_b`.
181    ///
182    /// This returns false if `feature_a` is the same as `feature_b`.
183    pub fn directly_depends_on<'a>(
184        &self,
185        feature_a: impl Into<FeatureId<'a>>,
186        feature_b: impl Into<FeatureId<'a>>,
187    ) -> Result<bool, Error> {
188        let feature_a = feature_a.into();
189        let feature_b = feature_b.into();
190        let a_ix = self.feature_ix(feature_a)?;
191        let b_ix = self.feature_ix(feature_b)?;
192        Ok(self.dep_graph().contains_edge(a_ix, b_ix))
193    }
194
195    /// Returns information about dependency cycles.
196    ///
197    /// For more information, see the documentation for `Cycles`.
198    pub fn cycles(&self) -> Cycles<'g> {
199        Cycles::new(*self)
200    }
201
202    // ---
203    // Helper methods
204    // ---
205
206    /// Verify basic properties of the feature graph.
207    #[doc(hidden)]
208    pub fn verify(&self) -> Result<(), Error> {
209        let feature_set = self.resolve_all();
210        for conditional_link in feature_set.conditional_links(DependencyDirection::Forward) {
211            let (from, to) = conditional_link.endpoints();
212            let is_any = conditional_link.normal().is_present()
213                || conditional_link.build().is_present()
214                || conditional_link.dev().is_present();
215
216            if !is_any {
217                return Err(Error::FeatureGraphInternalError(format!(
218                    "{} -> {}: no edge info found",
219                    from.feature_id(),
220                    to.feature_id()
221                )));
222            }
223        }
224
225        Ok(())
226    }
227
228    /// Returns the strongly connected components for this feature graph.
229    pub(super) fn sccs(&self) -> &'g Sccs<FeatureIx> {
230        self.inner.sccs.get_or_init(|| {
231            let edge_filtered =
232                EdgeFiltered::from_fn(self.dep_graph(), |edge| match edge.weight() {
233                    FeatureEdge::DependenciesSection(link)
234                    | FeatureEdge::NamedFeatureDepColon(link)
235                    | FeatureEdge::NamedFeatureWithSlash { link, .. } => !link.dev_only(),
236                    FeatureEdge::NamedFeature | FeatureEdge::FeatureToBase => true,
237                });
238            // Sort the entire graph without dev-only edges -- a correct graph would be cycle-free
239            // but we don't currently do a consistency check for this so handle cycles.
240            // TODO: should we check at construction time? or bubble up a warning somehow?
241            let topo = TopoWithCycles::new(&edge_filtered);
242            Sccs::new(&self.inner.graph, |scc| {
243                topo.sort_nodes(scc);
244            })
245        })
246    }
247
248    fn metadata_impl(&self, feature_id: FeatureId<'g>) -> Option<&'g FeatureMetadataImpl> {
249        let feature_node = FeatureNode::from_id(self, feature_id)?;
250        self.metadata_impl_for_node(&feature_node)
251    }
252
253    pub(in crate::graph) fn metadata_for_ix(
254        &self,
255        feature_ix: NodeIndex<FeatureIx>,
256    ) -> FeatureMetadata<'g> {
257        self.metadata_for_node(self.dep_graph()[feature_ix])
258            .expect("valid feature ix")
259    }
260
261    pub(super) fn metadata_for_node(&self, node: FeatureNode) -> Option<FeatureMetadata<'g>> {
262        let inner = self.metadata_impl_for_node(&node)?;
263        Some(FeatureMetadata {
264            graph: DebugIgnore(*self),
265            node,
266            inner,
267        })
268    }
269
270    #[inline]
271    fn metadata_impl_for_node(&self, node: &FeatureNode) -> Option<&'g FeatureMetadataImpl> {
272        self.inner.map.get(node)
273    }
274
275    pub(super) fn dep_graph(&self) -> &'g FeaturePetgraph {
276        &self.inner.graph
277    }
278
279    /// If this is a conditional edge, return the conditional link. Otherwise, return None.
280    pub(super) fn edge_to_conditional_link(
281        &self,
282        source_ix: NodeIndex<FeatureIx>,
283        target_ix: NodeIndex<FeatureIx>,
284        edge_ix: EdgeIndex<FeatureIx>,
285        edge: Option<&'g FeatureEdge>,
286    ) -> Option<(ConditionalLink<'g>, Option<WeakIndex>)> {
287        let edge = edge.unwrap_or_else(|| &self.dep_graph()[edge_ix]);
288
289        match edge {
290            FeatureEdge::NamedFeature | FeatureEdge::FeatureToBase => None,
291            FeatureEdge::DependenciesSection(link) | FeatureEdge::NamedFeatureDepColon(link) => {
292                let link = ConditionalLink::new(*self, source_ix, target_ix, edge_ix, link);
293                // Dependency section and dep:foo style conditional links are always non-weak.
294                let weak_index = None;
295                Some((link, weak_index))
296            }
297            FeatureEdge::NamedFeatureWithSlash { link, weak_index } => {
298                let link = ConditionalLink::new(*self, source_ix, target_ix, edge_ix, link);
299                Some((link, *weak_index))
300            }
301        }
302    }
303
304    fn feature_ix_depends_on(
305        &self,
306        a_ix: NodeIndex<FeatureIx>,
307        b_ix: NodeIndex<FeatureIx>,
308    ) -> bool {
309        has_path_connecting(self.dep_graph(), a_ix, b_ix, None)
310    }
311
312    fn feature_ix_depends_on_no_conditional(
313        &self,
314        a_ix: NodeIndex<FeatureIx>,
315        b_ix: NodeIndex<FeatureIx>,
316    ) -> bool {
317        // Filter out conditional edges.
318        let edge_filtered =
319            EdgeFiltered::from_fn(self.dep_graph(), |edge_ref| match edge_ref.weight() {
320                FeatureEdge::FeatureToBase | FeatureEdge::NamedFeature => true,
321                FeatureEdge::DependenciesSection(_)
322                | FeatureEdge::NamedFeatureDepColon(_)
323                | FeatureEdge::NamedFeatureWithSlash { .. } => false,
324            });
325        has_path_connecting(&edge_filtered, a_ix, b_ix, None)
326    }
327
328    pub(super) fn feature_ixs_for_package_ix(
329        &self,
330        package_ix: NodeIndex<PackageIx>,
331    ) -> impl Iterator<Item = NodeIndex<FeatureIx>> {
332        let package_ix = package_ix.index();
333        let base_ix = self.inner.base_ixs[package_ix].index();
334        // base_ixs has (package count + 1) elements so this access is valid.
335        let next_base_ix = self.inner.base_ixs[package_ix + 1].index();
336
337        (base_ix..next_base_ix).map(NodeIndex::new)
338    }
339
340    pub(super) fn feature_ixs_for_package_ixs(
341        &self,
342        package_ixs: impl IntoIterator<Item = NodeIndex<PackageIx>> + 'g,
343    ) -> impl Iterator<Item = NodeIndex<FeatureIx>> + 'g {
344        // Create a copy of FeatureGraph that will be moved into the closure below.
345        let this = *self;
346
347        package_ixs
348            .into_iter()
349            .flat_map(move |package_ix| this.feature_ixs_for_package_ix(package_ix))
350    }
351
352    pub(in crate::graph) fn feature_ixs_for_package_ixs_filtered<B>(
353        &self,
354        package_ixs: impl IntoIterator<Item = NodeIndex<PackageIx>>,
355        filter: impl FeatureFilter<'g>,
356    ) -> B
357    where
358        B: FromIterator<NodeIndex<FeatureIx>>,
359    {
360        let mut filter = filter;
361
362        self.feature_ixs_for_package_ixs(package_ixs)
363            .filter(|feature_ix| {
364                let feature_node = &self.dep_graph()[*feature_ix];
365                filter.accept(self, FeatureId::from_node(self.package_graph, feature_node))
366            })
367            .collect()
368    }
369
370    pub(in crate::graph) fn package_ix_for_feature_ix(
371        &self,
372        feature_ix: NodeIndex<FeatureIx>,
373    ) -> NodeIndex<PackageIx> {
374        let feature_node = &self.dep_graph()[feature_ix];
375        feature_node.package_ix()
376    }
377
378    #[allow(dead_code)]
379    pub(super) fn feature_ixs<'a, B>(
380        &self,
381        feature_ids: impl IntoIterator<Item = FeatureId<'g>>,
382    ) -> Result<B, Error>
383    where
384        B: iter::FromIterator<NodeIndex<FeatureIx>>,
385    {
386        feature_ids
387            .into_iter()
388            .map(|feature_id| self.feature_ix(feature_id))
389            .collect()
390    }
391
392    pub(super) fn feature_ix(
393        &self,
394        feature_id: FeatureId<'g>,
395    ) -> Result<NodeIndex<FeatureIx>, Error> {
396        let metadata = self
397            .metadata_impl(feature_id)
398            .ok_or_else(|| Error::unknown_feature_id(feature_id))?;
399        Ok(metadata.feature_ix)
400    }
401}
402
403/// An identifier for a (package, feature) pair in a feature graph.
404///
405/// Returned by various methods on `FeatureGraph` and `FeatureQuery`.
406///
407/// `From` impls are available for `(&'g PackageId, &'g str)` and `(&'g PackageId, Option<&'g str>)`
408/// tuples.
409#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
410pub struct FeatureId<'g> {
411    package_id: &'g PackageId,
412    label: FeatureLabel<'g>,
413}
414
415assert_covariant!(FeatureId);
416
417impl<'g> FeatureId<'g> {
418    /// Creates a new `FeatureId` with the given [`PackageId`] and [`FeatureLabel`].
419    pub fn new(package_id: &'g PackageId, label: FeatureLabel<'g>) -> Self {
420        Self { package_id, label }
421    }
422
423    /// Creates a new `FeatureId` representing a named feature in the `[features]` section,
424    /// or an implicit named feature created by an optional dependency.
425    pub fn named(package_id: &'g PackageId, feature_name: &'g str) -> Self {
426        Self {
427            package_id,
428            label: FeatureLabel::Named(feature_name),
429        }
430    }
431
432    /// Creates a new `FeatureId` representing an optional dependency.
433    pub fn optional_dependency(package_id: &'g PackageId, dep_name: &'g str) -> Self {
434        Self {
435            package_id,
436            label: FeatureLabel::OptionalDependency(dep_name),
437        }
438    }
439
440    /// Creates a new `FeatureId` representing the base feature for a package.
441    pub fn base(package_id: &'g PackageId) -> Self {
442        Self {
443            package_id,
444            label: FeatureLabel::Base,
445        }
446    }
447
448    pub(super) fn from_node(package_graph: &'g PackageGraph, node: &FeatureNode) -> Self {
449        let package_id = &package_graph.dep_graph[node.package_ix];
450        let metadata = package_graph
451            .metadata(package_id)
452            .expect("package ID should have valid metadata");
453        let feature = Self::node_to_feature(metadata, node);
454        Self {
455            package_id,
456            label: feature,
457        }
458    }
459
460    pub(super) fn node_to_feature(
461        metadata: PackageMetadata<'g>,
462        node: &FeatureNode,
463    ) -> FeatureLabel<'g> {
464        metadata.feature_idx_to_label(node.feature_idx)
465    }
466
467    /// Returns the package ID.
468    pub fn package_id(&self) -> &'g PackageId {
469        self.package_id
470    }
471
472    /// Returns the [`FeatureLabel`] associated with the feature.
473    pub fn label(&self) -> FeatureLabel<'g> {
474        self.label
475    }
476
477    /// Returns true if this is the base feature for the package.
478    #[inline]
479    pub fn is_base(&self) -> bool {
480        self.label.kind().is_base()
481    }
482
483    /// Returns true if this is an optional dependency.
484    #[inline]
485    pub fn is_optional_dependency(self) -> bool {
486        self.label.kind().is_optional_dependency()
487    }
488
489    /// Returns true if this is a named feature.
490    #[inline]
491    pub fn is_named(self) -> bool {
492        self.label.kind().is_named()
493    }
494}
495
496impl<'g> From<(&'g PackageId, FeatureLabel<'g>)> for FeatureId<'g> {
497    fn from((package_id, label): (&'g PackageId, FeatureLabel<'g>)) -> Self {
498        FeatureId { package_id, label }
499    }
500}
501
502/// The `Display` impl prints out:
503///
504/// * `{package-id}/[base]` for base features.
505/// * `{package-id}/feature-name` for named features.
506/// * `{package-id}/dep:dep-name` for optional dependencies.
507///
508/// ## Examples
509///
510/// ```
511/// use guppy::PackageId;
512/// use guppy::graph::feature::FeatureId;
513///
514/// let package_id = PackageId::new("region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)");
515///
516/// assert_eq!(
517///     format!("{}", FeatureId::base(&package_id)),
518///     "region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)/[base]"
519/// );
520///
521/// assert_eq!(
522///     format!("{}", FeatureId::named(&package_id, "foo")),
523///     "region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)/foo"
524/// );
525///
526/// assert_eq!(
527///     format!("{}", FeatureId::optional_dependency(&package_id, "bar")),
528///     "region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)/dep:bar"
529/// );
530/// ```
531impl fmt::Display for FeatureId<'_> {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        write!(f, "{}/{}", self.package_id, self.label)
534    }
535}
536
537/// A unique identifier for a feature within a specific package. Forms part of a [`FeatureId`].
538#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
539pub enum FeatureLabel<'g> {
540    /// The "base" feature. Every package has one such feature.
541    Base,
542
543    /// This is a named feature in the `[features]` section, or an implicit feature that corresponds to
544    /// an optional dependency.
545    ///
546    /// For versions of Cargo prior to 1.60, optional dependencies always create implicit features
547    /// by the same name. For versions 1.60 and greater, optional dependencies may create implicit
548    /// features if the dependency doesn't exist with the name "dep" in it.
549    Named(&'g str),
550
551    /// This is an optional dependency.
552    OptionalDependency(&'g str),
553}
554
555impl FeatureLabel<'_> {
556    /// Returns the kind of feature this is.
557    ///
558    /// The kind of a feature is simply the enum variant without any associated data.
559    #[inline]
560    pub fn kind(self) -> FeatureKind {
561        match self {
562            Self::Base => FeatureKind::Base,
563            Self::Named(_) => FeatureKind::Named,
564            Self::OptionalDependency(_) => FeatureKind::OptionalDependency,
565        }
566    }
567}
568
569/// The `Display` impl for `FeatureLabel` prints out:
570///
571/// * `[base]` for base labels.
572/// * `feature-name` for optional dependencies.
573/// * `dep:dep-name` for named features.
574///
575/// ## Examples
576///
577/// ```
578/// use guppy::graph::feature::FeatureLabel;
579///
580/// assert_eq!(format!("{}", FeatureLabel::Base), "[base]");
581/// assert_eq!(format!("{}", FeatureLabel::Named("foo")), "foo");
582/// assert_eq!(format!("{}", FeatureLabel::OptionalDependency("bar")), "dep:bar");
583/// ```
584impl fmt::Display for FeatureLabel<'_> {
585    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586        match self {
587            Self::Base => write!(f, "[base]"),
588            Self::Named(feature_name) => write!(f, "{}", feature_name),
589            Self::OptionalDependency(dep_name) => write!(f, "dep:{}", dep_name),
590        }
591    }
592}
593
594/// Metadata for a feature within a package.
595#[derive(Clone, Copy)]
596pub struct FeatureMetadata<'g> {
597    graph: DebugIgnore<FeatureGraph<'g>>,
598    node: FeatureNode,
599    inner: &'g FeatureMetadataImpl,
600}
601
602assert_covariant!(FeatureMetadata);
603
604impl<'g> FeatureMetadata<'g> {
605    /// Returns the feature ID corresponding to this metadata.
606    pub fn feature_id(&self) -> FeatureId<'g> {
607        FeatureId::from_node(self.graph.package_graph, &self.node)
608    }
609
610    /// Returns the package ID corresponding to this metadata.
611    pub fn package_id(&self) -> &'g PackageId {
612        &self.graph.package_graph.dep_graph[self.package_ix()]
613    }
614
615    /// Returns the package metadata corresponding to this feature metadata.
616    pub fn package(&self) -> PackageMetadata<'g> {
617        self.graph
618            .package_graph
619            .metadata(self.package_id())
620            .expect("valid package ID")
621    }
622
623    /// Returns the label for this feature.
624    pub fn label(&self) -> FeatureLabel<'g> {
625        self.feature_id().label()
626    }
627
628    // ---
629    // Helper methods
630    // ---
631
632    #[inline]
633    pub(in crate::graph) fn package_ix(&self) -> NodeIndex<PackageIx> {
634        self.node.package_ix
635    }
636
637    #[inline]
638    pub(in crate::graph) fn feature_ix(&self) -> NodeIndex<FeatureIx> {
639        self.inner.feature_ix
640    }
641}
642
643impl fmt::Debug for FeatureMetadata<'_> {
644    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
645        f.debug_struct("FeatureMetadata")
646            .field("id", &self.feature_id())
647            .finish()
648    }
649}
650
651/// A graph representing every possible feature of every package, and the connections between them.
652#[derive(Clone, Debug)]
653pub(in crate::graph) struct FeatureGraphImpl {
654    pub(super) graph: FeaturePetgraph,
655    // base ixs consists of the base (start) feature indexes for each package.
656    pub(super) base_ixs: Vec<NodeIndex<FeatureIx>>,
657    pub(super) map: AHashMap<FeatureNode, FeatureMetadataImpl>,
658    pub(super) warnings: Vec<FeatureGraphWarning>,
659    // The strongly connected components of the feature graph. Computed on demand.
660    pub(super) sccs: OnceCell<Sccs<FeatureIx>>,
661    pub(super) weak: WeakDependencies,
662}
663
664impl FeatureGraphImpl {
665    /// Creates a new `FeatureGraph` from this `PackageGraph`.
666    pub(super) fn new(package_graph: &PackageGraph) -> Self {
667        let mut build_state = FeatureGraphBuildState::new(package_graph);
668
669        // Graph returns its node references in order -- check this in debug builds.
670        let mut prev_ix = None;
671        for (package_ix, package_id) in package_graph.dep_graph.node_references() {
672            if let Some(prev_ix) = prev_ix {
673                debug_assert_eq!(package_ix.index(), prev_ix + 1, "package ixs are in order");
674            }
675            prev_ix = Some(package_ix.index());
676
677            let metadata = package_graph
678                .metadata(package_id)
679                .expect("valid package ID");
680            build_state.add_nodes(metadata);
681        }
682
683        build_state.end_nodes();
684
685        // The choice of bottom-up for this loop and the next is pretty arbitrary.
686        for metadata in package_graph
687            .resolve_all()
688            .packages(DependencyDirection::Reverse)
689        {
690            build_state.add_named_feature_edges(metadata);
691        }
692
693        for link in package_graph
694            .resolve_all()
695            .links(DependencyDirection::Reverse)
696        {
697            build_state.add_dependency_edges(link);
698        }
699
700        build_state.build()
701    }
702}
703
704/// A feature dependency that is conditionally activated.
705///
706/// A `ConditionalLink` is typically a link across packages. For example:
707///
708/// ```toml
709/// [package]
710/// name = "main"
711///
712/// [dependencies]
713/// dep = { ... }
714///
715/// [dev-dependencies]
716/// dev-dep = { ... }
717///
718/// [target.'cfg(unix)'.dependencies]
719/// unix-dep = { ... }
720///
721/// [features]
722/// feat = ["dep/feat", "dev-dep/feat", "unix-dep/feat"]
723/// ```
724///
725/// In this example, there are `ConditionalLink`s from `main/feat` to `dep/feat`, `dev-dep/feat` and
726/// `unix-dep/feat`. Each link is only activated if the conditions for it are met. For example,
727/// the link to `dev-dep/feat` is only followed if Cargo is interested in dev-dependencies of `main`.
728///
729/// If a dependency, for example `unix-dep` above, is optional, an implicit feature is created in
730/// the package `main` with the name `unix-dep`. In this case, the dependency from `main/feat` to
731/// `main/unix-dep` is also a `ConditionalLink` representing the same `cfg(unix)` condition.
732#[derive(Copy, Clone)]
733pub struct ConditionalLink<'g> {
734    graph: DebugIgnore<FeatureGraph<'g>>,
735    from: &'g FeatureMetadataImpl,
736    to: &'g FeatureMetadataImpl,
737    edge_ix: EdgeIndex<FeatureIx>,
738    inner: &'g ConditionalLinkImpl,
739}
740
741assert_covariant!(ConditionalLink);
742
743impl<'g> ConditionalLink<'g> {
744    #[allow(dead_code)]
745    pub(super) fn new(
746        graph: FeatureGraph<'g>,
747        source_ix: NodeIndex<FeatureIx>,
748        target_ix: NodeIndex<FeatureIx>,
749        edge_ix: EdgeIndex<FeatureIx>,
750        inner: &'g ConditionalLinkImpl,
751    ) -> Self {
752        let dep_graph = graph.dep_graph();
753        Self {
754            graph: DebugIgnore(graph),
755            from: graph
756                .metadata_impl_for_node(&dep_graph[source_ix])
757                .expect("valid source ix"),
758            to: graph
759                .metadata_impl_for_node(&dep_graph[target_ix])
760                .expect("valid target ix"),
761            edge_ix,
762            inner,
763        }
764    }
765
766    /// Returns the feature which depends on the `to` feature.
767    pub fn from(&self) -> FeatureMetadata<'g> {
768        FeatureMetadata {
769            graph: DebugIgnore(self.graph.0),
770            node: self.graph.dep_graph()[self.from.feature_ix],
771            inner: self.from,
772        }
773    }
774
775    /// Returns the feature which is depended on by the `from` feature.
776    pub fn to(&self) -> FeatureMetadata<'g> {
777        FeatureMetadata {
778            graph: DebugIgnore(self.graph.0),
779            node: self.graph.dep_graph()[self.to.feature_ix],
780            inner: self.to,
781        }
782    }
783
784    /// Returns the endpoints as a pair of features `(from, to)`.
785    pub fn endpoints(&self) -> (FeatureMetadata<'g>, FeatureMetadata<'g>) {
786        (self.from(), self.to())
787    }
788
789    /// Returns details about this feature dependency from the `[dependencies]` section.
790    pub fn normal(&self) -> PlatformStatus<'g> {
791        PlatformStatus::new(&self.inner.normal)
792    }
793
794    /// Returns details about this feature dependency from the `[build-dependencies]` section.
795    pub fn build(&self) -> PlatformStatus<'g> {
796        PlatformStatus::new(&self.inner.build)
797    }
798
799    /// Returns details about this feature dependency from the `[dev-dependencies]` section.
800    pub fn dev(&self) -> PlatformStatus<'g> {
801        PlatformStatus::new(&self.inner.dev)
802    }
803
804    /// Returns details about this feature dependency from the section specified by the given
805    /// dependency kind.
806    pub fn status_for_kind(&self, kind: DependencyKind) -> PlatformStatus<'g> {
807        match kind {
808            DependencyKind::Normal => self.normal(),
809            DependencyKind::Build => self.build(),
810            DependencyKind::Development => self.dev(),
811        }
812    }
813
814    /// Returns true if this edge is dev-only, i.e. code from this edge will not be included in
815    /// normal builds.
816    pub fn dev_only(&self) -> bool {
817        self.inner.dev_only()
818    }
819
820    /// Returns the `PackageLink` from which this `ConditionalLink` was derived.
821    pub fn package_link(&self) -> PackageLink<'g> {
822        self.graph
823            .package_graph
824            .edge_ix_to_link(self.inner.package_edge_ix)
825    }
826
827    // ---
828    // Helper methods
829    // ---
830
831    #[allow(dead_code)]
832    pub(super) fn edge_ix(&self) -> EdgeIndex<FeatureIx> {
833        self.edge_ix
834    }
835
836    #[allow(dead_code)]
837    pub(in crate::graph) fn package_edge_ix(&self) -> EdgeIndex<PackageIx> {
838        self.inner.package_edge_ix
839    }
840}
841
842impl fmt::Debug for ConditionalLink<'_> {
843    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844        f.debug_struct("ConditionalLink")
845            .field("from", &self.from())
846            .field("to", &self.to())
847            .field("normal", &self.normal())
848            .field("build", &self.build())
849            .field("dev", &self.dev())
850            .finish()
851    }
852}
853
854// ---
855
856/// A combination of a package ID and a feature name, forming a node in a `FeatureGraph`.
857#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
858pub(in crate::graph) struct FeatureNode {
859    package_ix: NodeIndex<PackageIx>,
860    feature_idx: FeatureIndexInPackage,
861}
862
863impl FeatureNode {
864    /// Returns a new feature node.
865    pub(in crate::graph) fn new(
866        package_ix: NodeIndex<PackageIx>,
867        feature_idx: FeatureIndexInPackage,
868    ) -> Self {
869        Self {
870            package_ix,
871            feature_idx,
872        }
873    }
874
875    /// Base feature node.
876    pub(in crate::graph) fn base(package_ix: NodeIndex<PackageIx>) -> Self {
877        Self {
878            package_ix,
879            feature_idx: FeatureIndexInPackage::Base,
880        }
881    }
882
883    pub(in crate::graph) fn optional_dep(package_ix: NodeIndex<PackageIx>, dep_idx: usize) -> Self {
884        Self {
885            package_ix,
886            feature_idx: FeatureIndexInPackage::OptionalDependency(dep_idx),
887        }
888    }
889
890    pub(in crate::graph) fn named_feature(
891        package_ix: NodeIndex<PackageIx>,
892        named_idx: usize,
893    ) -> Self {
894        Self {
895            package_ix,
896            feature_idx: FeatureIndexInPackage::Named(named_idx),
897        }
898    }
899
900    fn from_id(feature_graph: &FeatureGraph<'_>, id: FeatureId<'_>) -> Option<Self> {
901        let metadata = feature_graph.package_graph.metadata(id.package_id()).ok()?;
902        Some(FeatureNode::new(
903            metadata.package_ix(),
904            metadata.get_feature_idx(id.label())?,
905        ))
906    }
907
908    pub(super) fn named_features(package: PackageMetadata<'_>) -> impl Iterator<Item = Self> + '_ {
909        let package_ix = package.package_ix();
910        package
911            .named_features_full()
912            .map(move |(feature_idx, _, _)| Self {
913                package_ix,
914                feature_idx,
915            })
916    }
917
918    pub(super) fn optional_deps(package: PackageMetadata<'_>) -> impl Iterator<Item = Self> + '_ {
919        let package_ix = package.package_ix();
920        package
921            .optional_deps_full()
922            .map(move |(feature_idx, _)| Self {
923                package_ix,
924                feature_idx,
925            })
926    }
927
928    pub(in crate::graph) fn package_ix(&self) -> NodeIndex<PackageIx> {
929        self.package_ix
930    }
931
932    pub(in crate::graph) fn package_id_and_feature_label<'g>(
933        &self,
934        graph: &'g PackageGraph,
935    ) -> (&'g PackageId, FeatureLabel<'g>) {
936        let package_id = &graph.dep_graph[self.package_ix];
937        let metadata = graph.metadata(package_id).unwrap();
938        let feature_label = metadata.feature_idx_to_label(self.feature_idx);
939        (package_id, feature_label)
940    }
941}
942
943/// Information about why a feature depends on another feature.
944///
945/// Not part of the stable API -- only exposed for FeatureSet::links().
946#[derive(Clone, Debug)]
947#[doc(hidden)]
948pub enum FeatureEdge {
949    /// This edge is from a feature to its base package.
950    FeatureToBase,
951
952    /// This is a dependency edge, e.g.:
953    ///
954    /// ```toml
955    /// [dependencies]
956    /// foo = { version = "1", features = ["a", "b"] }
957    /// ```
958    ///
959    /// (The above is conditional in that it isn't a build dependency. Similarly, it could be
960    /// a target-specific dependency.)
961    ///
962    /// This also includes optional dependencies, for which the "from" node is
963    /// `FeatureLabel::OptionalDependency` rather than `FeatureLabel::Base`.
964    ///
965    /// ```toml
966    /// [dependencies]
967    /// foo = { version = "1", features = ["a", "b"], optional = true }
968    /// ```
969    DependenciesSection(ConditionalLinkImpl),
970
971    /// This edge is from a feature depending on other features within the same package:
972    ///
973    /// ```toml
974    /// [features]
975    /// a = ["b"]
976    /// ```
977    NamedFeature,
978
979    /// This edge is from a feature to an optional dependency.
980    ///
981    /// ```toml
982    /// [features]
983    /// a = ["dep:foo"]
984    /// ```
985    NamedFeatureDepColon(ConditionalLinkImpl),
986
987    /// This is a named feature line of the form
988    ///
989    /// ```toml
990    /// [features]
991    /// a = ["foo/b"]
992    /// # or
993    /// a = ["foo?/b"]
994    /// ```
995    NamedFeatureWithSlash {
996        link: ConditionalLinkImpl,
997        weak_index: Option<WeakIndex>,
998    },
999}
1000
1001/// Not part of the stable API -- only exposed for FeatureSet::links().
1002#[derive(Clone, Debug)]
1003#[doc(hidden)]
1004pub struct ConditionalLinkImpl {
1005    pub(super) package_edge_ix: EdgeIndex<PackageIx>,
1006    pub(super) normal: PlatformStatusImpl,
1007    pub(super) build: PlatformStatusImpl,
1008    pub(super) dev: PlatformStatusImpl,
1009}
1010
1011impl ConditionalLinkImpl {
1012    #[inline]
1013    fn dev_only(&self) -> bool {
1014        self.normal.is_never() && self.build.is_never()
1015    }
1016}
1017
1018/// Metadata for a particular feature node.
1019#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1020pub(super) struct FeatureMetadataImpl {
1021    pub(super) feature_ix: NodeIndex<FeatureIx>,
1022}
1023
1024/// The kind of a particular feature within a package.
1025///
1026/// Returned by `FeatureMetadata`.
1027#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1028pub enum FeatureKind {
1029    /// The "base" feature. Every package has one such feature.
1030    Base,
1031
1032    /// This is a named feature in the `[features]` section, or an implicit feature that corresponds to
1033    /// an optional dependency.
1034    ///
1035    /// For versions of Cargo prior to 1.60, optional dependencies always create implicit features
1036    /// by the same name. For versions 1.60 and greater, optional dependencies may create implicit
1037    /// features if the dependency doesn't exist with the name "dep" in it.
1038    Named,
1039
1040    /// This is an optional dependency.
1041    OptionalDependency,
1042}
1043
1044impl FeatureKind {
1045    /// Returns true if this is the base feature.
1046    #[inline]
1047    pub fn is_base(self) -> bool {
1048        matches!(self, Self::Base)
1049    }
1050
1051    /// Returns true if this is a named feature.
1052    #[inline]
1053    pub fn is_named(self) -> bool {
1054        matches!(self, Self::Named)
1055    }
1056
1057    /// Returns true if this is an optional dependency.
1058    #[inline]
1059    pub fn is_optional_dependency(self) -> bool {
1060        matches!(self, Self::OptionalDependency)
1061    }
1062}