guppy 0.18.0

Track and query Cargo dependency graphs.
Documentation
// Copyright (c) The cargo-guppy Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
    Error, PackageId,
    graph::{
        DependencyDirection, LinkVisitorFn, PackageGraph, PackageIx, PackageLink,
        PackageLinkContext, PackageLinkVisitor, PackageSet,
        feature::{FeatureFilter, FeatureQuery},
    },
};
use camino::Utf8Path;
use petgraph::prelude::*;

/// A query over a package graph.
///
/// This is the entry point for iterators over IDs and dependency links, and dot graph presentation.
/// A `PackageQuery` is constructed through the `query_` methods on `PackageGraph`.
#[derive(Clone, Debug)]
pub struct PackageQuery<'g> {
    // The fields are pub(super) for access within the graph module.
    pub(super) initials: PackageSet<'g>,
    pub(super) direction: DependencyDirection,
}

assert_covariant!(PackageQuery);

/// ## Queries
///
/// The methods in this section create *queries* over subsets of this package graph. Use the methods
/// here to analyze transitive dependencies.
impl PackageGraph {
    /// Creates a new forward query over the entire workspace.
    ///
    /// `query_workspace` will select all workspace packages and their transitive dependencies. To
    /// create a `PackageSet` with just workspace packages, use `resolve_workspace`.
    pub fn query_workspace(&self) -> PackageQuery<'_> {
        self.query_forward(self.workspace().member_ids())
            .expect("workspace packages should all be known")
    }

    /// Creates a new forward query over the specified workspace packages by path.
    ///
    /// Returns an error if any workspace paths were unknown.
    pub fn query_workspace_paths(
        &self,
        paths: impl IntoIterator<Item = impl AsRef<Utf8Path>>,
    ) -> Result<PackageQuery<'_>, Error> {
        let workspace = self.workspace();
        let package_ixs = paths
            .into_iter()
            .map(|path| {
                workspace
                    .member_by_path(path.as_ref())
                    .map(|package| package.package_ix())
            })
            .collect::<Result<Vec<_>, Error>>()?;

        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
    }

    /// Creates a new forward query over the specified workspace packages by name.
    ///
    /// This is similar to `cargo`'s `--package` option.
    ///
    /// Returns an error if any package names were unknown.
    pub fn query_workspace_names(
        &self,
        names: impl IntoIterator<Item = impl AsRef<str>>,
    ) -> Result<PackageQuery<'_>, Error> {
        let workspace = self.workspace();
        let package_ixs = names
            .into_iter()
            .map(|name| {
                workspace
                    .member_by_name(name.as_ref())
                    .map(|package| package.package_ix())
            })
            .collect::<Result<Vec<_>, Error>>()?;

        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
    }

    /// Creates a new query that returns transitive dependencies of the given packages in the
    /// specified direction.
    ///
    /// Returns an error if any package IDs are unknown.
    pub fn query_directed<'g, 'a>(
        &'g self,
        package_ids: impl IntoIterator<Item = &'a PackageId>,
        dep_direction: DependencyDirection,
    ) -> Result<PackageQuery<'g>, Error> {
        match dep_direction {
            DependencyDirection::Forward => self.query_forward(package_ids),
            DependencyDirection::Reverse => self.query_reverse(package_ids),
        }
    }

    /// Creates a new query that returns transitive dependencies of the given packages.
    ///
    /// Returns an error if any package IDs are unknown.
    pub fn query_forward<'g, 'a>(
        &'g self,
        package_ids: impl IntoIterator<Item = &'a PackageId>,
    ) -> Result<PackageQuery<'g>, Error> {
        let package_ixs: Vec<_> = self.package_ixs(package_ids)?;
        Ok(self.query_from_parts(package_ixs, DependencyDirection::Forward))
    }

    /// Creates a new query that returns transitive reverse dependencies of the given packages.
    ///
    /// Returns an error if any package IDs are unknown.
    pub fn query_reverse<'g, 'a>(
        &'g self,
        package_ids: impl IntoIterator<Item = &'a PackageId>,
    ) -> Result<PackageQuery<'g>, Error> {
        let package_ixs: Vec<_> = self.package_ixs(package_ids)?;
        Ok(self.query_from_parts(package_ixs, DependencyDirection::Reverse))
    }

    pub(super) fn query_from_parts(
        &self,
        package_ixs: impl IntoIterator<Item = NodeIndex<PackageIx>>,
        direction: DependencyDirection,
    ) -> PackageQuery<'_> {
        PackageQuery {
            initials: PackageSet::from_ixs(self, package_ixs),
            direction,
        }
    }
}

impl<'g> PackageQuery<'g> {
    /// Returns the package graph on which the query is going to be executed.
    pub fn graph(&self) -> &'g PackageGraph {
        self.initials.graph()
    }

    /// Returns the direction the query is happening in.
    pub fn direction(&self) -> DependencyDirection {
        self.direction
    }

    /// Returns the set of initial packages specified in the query.
    pub fn initials(&self) -> &PackageSet<'g> {
        &self.initials
    }

    /// Converts this `PackageQuery` into a `FeatureQuery`, using the given feature filter.
    ///
    /// This will cause the feature graph to be constructed if it hasn't been done so already.
    pub fn to_feature_query(&self, filter: impl FeatureFilter<'g>) -> FeatureQuery<'g> {
        let feature_graph = self.graph().feature_graph();
        let feature_ixs: Vec<_> =
            feature_graph.feature_ixs_for_package_ixs_filtered(self.initials.sorted_ixs(), filter);
        feature_graph.query_from_parts(feature_ixs, self.direction)
    }

    /// Resolves this query into a set of known packages, following every link found along the
    /// way.
    ///
    /// This is the entry point for iterators.
    pub fn resolve(self) -> PackageSet<'g> {
        PackageSet::new(self)
    }

    /// Resolves this query into a set of known packages, using the provided visitor to
    /// determine which links are followed.
    pub fn resolve_with(self, visitor: impl PackageLinkVisitor<'g>) -> PackageSet<'g> {
        PackageSet::with_link_visitor(self, visitor)
    }

    /// Resolves this query into a set of known packages, using the provided visitor function
    /// to determine which links are followed.
    pub fn resolve_with_fn(
        self,
        visitor_fn: impl FnMut(&PackageLinkContext<'g>, PackageLink<'g>) -> bool,
    ) -> PackageSet<'g> {
        self.resolve_with(LinkVisitorFn(visitor_fn))
    }
}