Skip to main content

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