1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
34use crate::{
5 graph::{
6 feature::{FeatureFilter, FeatureQuery},
7 query_core::QueryParams,
8 DependencyDirection, PackageGraph, PackageIx, PackageLink, PackageMetadata,
9 PackageResolver, PackageSet, ResolverFn,
10 },
11 sorted_set::SortedSet,
12 Error, PackageId,
13};
14use camino::Utf8Path;
15use petgraph::prelude::*;
1617/// A query over a package graph.
18///
19/// This is the entry point for iterators over IDs and dependency links, and dot graph presentation.
20/// A `PackageQuery` is constructed through the `query_` methods on `PackageGraph`.
21#[derive(Clone, Debug)]
22pub struct PackageQuery<'g> {
23// The fields are pub(super) for access within the graph module.
24pub(super) graph: &'g PackageGraph,
25pub(super) params: QueryParams<PackageGraph>,
26}
2728assert_covariant!(PackageQuery);
2930/// ## Queries
31///
32/// The methods in this section create *queries* over subsets of this package graph. Use the methods
33/// here to analyze transitive dependencies.
34impl PackageGraph {
35/// Creates a new forward query over the entire workspace.
36 ///
37 /// `query_workspace` will select all workspace packages and their transitive dependencies. To
38 /// create a `PackageSet` with just workspace packages, use `resolve_workspace`.
39pub fn query_workspace(&self) -> PackageQuery {
40self.query_forward(self.workspace().member_ids())
41 .expect("workspace packages should all be known")
42 }
4344/// Creates a new forward query over the specified workspace packages by path.
45 ///
46 /// Returns an error if any workspace paths were unknown.
47pub fn query_workspace_paths(
48&self,
49 paths: impl IntoIterator<Item = impl AsRef<Utf8Path>>,
50 ) -> Result<PackageQuery, Error> {
51let workspace = self.workspace();
52let package_ixs = paths
53 .into_iter()
54 .map(|path| {
55 workspace
56 .member_by_path(path.as_ref())
57 .map(|package| package.package_ix())
58 })
59 .collect::<Result<SortedSet<_>, Error>>()?;
6061Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
62 }
6364/// Creates a new forward query over the specified workspace packages by name.
65 ///
66 /// This is similar to `cargo`'s `--package` option.
67 ///
68 /// Returns an error if any package names were unknown.
69pub fn query_workspace_names(
70&self,
71 names: impl IntoIterator<Item = impl AsRef<str>>,
72 ) -> Result<PackageQuery, Error> {
73let workspace = self.workspace();
74let package_ixs = names
75 .into_iter()
76 .map(|name| {
77 workspace
78 .member_by_name(name.as_ref())
79 .map(|package| package.package_ix())
80 })
81 .collect::<Result<SortedSet<_>, Error>>()?;
8283Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
84 }
8586/// Creates a new query that returns transitive dependencies of the given packages in the
87 /// specified direction.
88 ///
89 /// Returns an error if any package IDs are unknown.
90pub fn query_directed<'g, 'a>(
91&'g self,
92 package_ids: impl IntoIterator<Item = &'a PackageId>,
93 dep_direction: DependencyDirection,
94 ) -> Result<PackageQuery<'g>, Error> {
95match dep_direction {
96 DependencyDirection::Forward => self.query_forward(package_ids),
97 DependencyDirection::Reverse => self.query_reverse(package_ids),
98 }
99 }
100101/// Creates a new query that returns transitive dependencies of the given packages.
102 ///
103 /// Returns an error if any package IDs are unknown.
104pub fn query_forward<'g, 'a>(
105&'g self,
106 package_ids: impl IntoIterator<Item = &'a PackageId>,
107 ) -> Result<PackageQuery<'g>, Error> {
108Ok(PackageQuery {
109 graph: self,
110 params: QueryParams::Forward(self.package_ixs(package_ids)?),
111 })
112 }
113114/// Creates a new query that returns transitive reverse dependencies of the given packages.
115 ///
116 /// Returns an error if any package IDs are unknown.
117pub fn query_reverse<'g, 'a>(
118&'g self,
119 package_ids: impl IntoIterator<Item = &'a PackageId>,
120 ) -> Result<PackageQuery<'g>, Error> {
121Ok(PackageQuery {
122 graph: self,
123 params: QueryParams::Reverse(self.package_ixs(package_ids)?),
124 })
125 }
126127pub(super) fn query_from_parts(
128&self,
129 package_ixs: SortedSet<NodeIndex<PackageIx>>,
130 direction: DependencyDirection,
131 ) -> PackageQuery {
132let params = match direction {
133 DependencyDirection::Forward => QueryParams::Forward(package_ixs),
134 DependencyDirection::Reverse => QueryParams::Reverse(package_ixs),
135 };
136 PackageQuery {
137 graph: self,
138 params,
139 }
140 }
141}
142143impl<'g> PackageQuery<'g> {
144/// Returns the package graph on which the query is going to be executed.
145pub fn graph(&self) -> &'g PackageGraph {
146self.graph
147 }
148149/// Returns the direction the query is happening in.
150pub fn direction(&self) -> DependencyDirection {
151self.params.direction()
152 }
153154/// Returns the list of initial packages specified in the query.
155 ///
156 /// The order of packages is unspecified.
157pub fn initials<'a>(&'a self) -> impl ExactSizeIterator<Item = PackageMetadata<'g>> + 'a {
158let graph = self.graph;
159self.params.initials().iter().map(move |package_ix| {
160 graph
161 .metadata(&graph.dep_graph[*package_ix])
162 .expect("valid ID")
163 })
164 }
165166/// Returns true if the query starts from the given package ID.
167 ///
168 /// Returns an error if this package ID is unknown.
169pub fn starts_from(&self, package_id: &PackageId) -> Result<bool, Error> {
170Ok(self.params.has_initial(self.graph.package_ix(package_id)?))
171 }
172173/// Converts this `PackageQuery` into a `FeatureQuery`, using the given feature filter.
174 ///
175 /// This will cause the feature graph to be constructed if it hasn't been done so already.
176pub fn to_feature_query(&self, filter: impl FeatureFilter<'g>) -> FeatureQuery<'g> {
177let package_ixs = self.params.initials();
178let feature_graph = self.graph.feature_graph();
179let feature_ixs =
180 feature_graph.feature_ixs_for_package_ixs_filtered(package_ixs.iter().copied(), filter);
181 feature_graph.query_from_parts(feature_ixs, self.direction())
182 }
183184/// Resolves this query into a set of known packages, following every link found along the
185 /// way.
186 ///
187 /// This is the entry point for iterators.
188pub fn resolve(self) -> PackageSet<'g> {
189 PackageSet::new(self)
190 }
191192/// Resolves this query into a set of known packages, using the provided resolver to
193 /// determine which links are followed.
194pub fn resolve_with(self, resolver: impl PackageResolver<'g>) -> PackageSet<'g> {
195 PackageSet::with_resolver(self, resolver)
196 }
197198/// Resolves this query into a set of known packages, using the provided resolver function
199 /// to determine which links are followed.
200pub fn resolve_with_fn(
201self,
202 resolver_fn: impl FnMut(&PackageQuery<'g>, PackageLink<'g>) -> bool,
203 ) -> PackageSet<'g> {
204self.resolve_with(ResolverFn(resolver_fn))
205 }
206}