Skip to main content

guppy/graph/
query.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    Error, PackageId,
6    graph::{
7        DependencyDirection, LinkVisitorFn, PackageGraph, PackageIx, PackageLink,
8        PackageLinkContext, PackageLinkVisitor, PackageSet,
9        feature::{FeatureFilter, FeatureQuery},
10    },
11};
12use camino::Utf8Path;
13use petgraph::prelude::*;
14
15/// A query over a package graph.
16///
17/// This is the entry point for iterators over IDs and dependency links, and dot graph presentation.
18/// A `PackageQuery` is constructed through the `query_` methods on `PackageGraph`.
19#[derive(Clone, Debug)]
20pub struct PackageQuery<'g> {
21    // The fields are pub(super) for access within the graph module.
22    pub(super) initials: PackageSet<'g>,
23    pub(super) direction: DependencyDirection,
24}
25
26assert_covariant!(PackageQuery);
27
28/// ## Queries
29///
30/// The methods in this section create *queries* over subsets of this package graph. Use the methods
31/// here to analyze transitive dependencies.
32impl PackageGraph {
33    /// Creates a new forward query over the entire workspace.
34    ///
35    /// `query_workspace` will select all workspace packages and their transitive dependencies. To
36    /// create a `PackageSet` with just workspace packages, use `resolve_workspace`.
37    pub fn query_workspace(&self) -> PackageQuery<'_> {
38        self.query_forward(self.workspace().member_ids())
39            .expect("workspace packages should all be known")
40    }
41
42    /// Creates a new forward query over the specified workspace packages by path.
43    ///
44    /// Returns an error if any workspace paths were unknown.
45    pub fn query_workspace_paths(
46        &self,
47        paths: impl IntoIterator<Item = impl AsRef<Utf8Path>>,
48    ) -> Result<PackageQuery<'_>, Error> {
49        let workspace = self.workspace();
50        let package_ixs = paths
51            .into_iter()
52            .map(|path| {
53                workspace
54                    .member_by_path(path.as_ref())
55                    .map(|package| package.package_ix())
56            })
57            .collect::<Result<Vec<_>, Error>>()?;
58
59        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
60    }
61
62    /// Creates a new forward query over the specified workspace packages by name.
63    ///
64    /// This is similar to `cargo`'s `--package` option.
65    ///
66    /// Returns an error if any package names were unknown.
67    pub fn query_workspace_names(
68        &self,
69        names: impl IntoIterator<Item = impl AsRef<str>>,
70    ) -> Result<PackageQuery<'_>, Error> {
71        let workspace = self.workspace();
72        let package_ixs = names
73            .into_iter()
74            .map(|name| {
75                workspace
76                    .member_by_name(name.as_ref())
77                    .map(|package| package.package_ix())
78            })
79            .collect::<Result<Vec<_>, Error>>()?;
80
81        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
82    }
83
84    /// Creates a new query that returns transitive dependencies of the given packages in the
85    /// specified direction.
86    ///
87    /// Returns an error if any package IDs are unknown.
88    pub fn query_directed<'g, 'a>(
89        &'g self,
90        package_ids: impl IntoIterator<Item = &'a PackageId>,
91        dep_direction: DependencyDirection,
92    ) -> Result<PackageQuery<'g>, Error> {
93        match dep_direction {
94            DependencyDirection::Forward => self.query_forward(package_ids),
95            DependencyDirection::Reverse => self.query_reverse(package_ids),
96        }
97    }
98
99    /// Creates a new query that returns transitive dependencies of the given packages.
100    ///
101    /// Returns an error if any package IDs are unknown.
102    pub fn query_forward<'g, 'a>(
103        &'g self,
104        package_ids: impl IntoIterator<Item = &'a PackageId>,
105    ) -> Result<PackageQuery<'g>, Error> {
106        let package_ixs: Vec<_> = self.package_ixs(package_ids)?;
107        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
108    }
109
110    /// Creates a new query that returns transitive reverse dependencies of the given packages.
111    ///
112    /// Returns an error if any package IDs are unknown.
113    pub fn query_reverse<'g, 'a>(
114        &'g self,
115        package_ids: impl IntoIterator<Item = &'a PackageId>,
116    ) -> Result<PackageQuery<'g>, Error> {
117        let package_ixs: Vec<_> = self.package_ixs(package_ids)?;
118        Ok(self.query_from_parts(package_ixs, DependencyDirection::Reverse))
119    }
120
121    pub(super) fn query_from_parts(
122        &self,
123        package_ixs: impl IntoIterator<Item = NodeIndex<PackageIx>>,
124        direction: DependencyDirection,
125    ) -> PackageQuery<'_> {
126        PackageQuery {
127            initials: PackageSet::from_ixs(self, package_ixs),
128            direction,
129        }
130    }
131}
132
133impl<'g> PackageQuery<'g> {
134    /// Returns the package graph on which the query is going to be executed.
135    pub fn graph(&self) -> &'g PackageGraph {
136        self.initials.graph()
137    }
138
139    /// Returns the direction the query is happening in.
140    pub fn direction(&self) -> DependencyDirection {
141        self.direction
142    }
143
144    /// Returns the set of initial packages specified in the query.
145    pub fn initials(&self) -> &PackageSet<'g> {
146        &self.initials
147    }
148
149    /// Converts this `PackageQuery` into a `FeatureQuery`, using the given feature filter.
150    ///
151    /// This will cause the feature graph to be constructed if it hasn't been done so already.
152    pub fn to_feature_query(&self, filter: impl FeatureFilter<'g>) -> FeatureQuery<'g> {
153        let feature_graph = self.graph().feature_graph();
154        let feature_ixs: Vec<_> =
155            feature_graph.feature_ixs_for_package_ixs_filtered(self.initials.sorted_ixs(), filter);
156        feature_graph.query_from_parts(feature_ixs, self.direction)
157    }
158
159    /// Resolves this query into a set of known packages, following every link found along the
160    /// way.
161    ///
162    /// This is the entry point for iterators.
163    pub fn resolve(self) -> PackageSet<'g> {
164        PackageSet::new(self)
165    }
166
167    /// Resolves this query into a set of known packages, using the provided visitor to
168    /// determine which links are followed.
169    pub fn resolve_with(self, visitor: impl PackageLinkVisitor<'g>) -> PackageSet<'g> {
170        PackageSet::with_link_visitor(self, visitor)
171    }
172
173    /// Resolves this query into a set of known packages, using the provided visitor function
174    /// to determine which links are followed.
175    pub fn resolve_with_fn(
176        self,
177        visitor_fn: impl FnMut(&PackageLinkContext<'g>, PackageLink<'g>) -> bool,
178    ) -> PackageSet<'g> {
179        self.resolve_with(LinkVisitorFn(visitor_fn))
180    }
181}