guppy/graph/
graph_impl.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    CargoMetadata, DependencyKind, Error, JsonValue, MetadataCommand, PackageId,
6    graph::{
7        BuildTarget, BuildTargetId, BuildTargetImpl, BuildTargetKind, Cycles, DependencyDirection,
8        OwnedBuildTargetId, PackageIx, PackageQuery, PackageSet, cargo_version_matches,
9        feature::{FeatureGraphImpl, FeatureId, FeatureLabel, FeatureNode},
10    },
11    petgraph_support::{IxBitSet, scc::Sccs, topo::TopoWithCycles},
12    platform::{EnabledTernary, PlatformSpec, PlatformStatus, PlatformStatusImpl},
13};
14use ahash::AHashMap;
15use camino::{Utf8Path, Utf8PathBuf};
16use fixedbitset::FixedBitSet;
17use indexmap::{IndexMap, IndexSet};
18use once_cell::sync::OnceCell;
19use petgraph::{
20    algo::{DfsSpace, has_path_connecting},
21    graph::EdgeReference,
22    prelude::*,
23    visit::EdgeFiltered,
24};
25use semver::{Version, VersionReq};
26use smallvec::SmallVec;
27use std::{
28    collections::{BTreeMap, HashSet},
29    fmt, iter,
30    iter::FromIterator,
31};
32
33use super::feature::{FeatureFilter, FeatureSet};
34
35/// A graph of packages and dependencies between them, parsed from metadata returned by `cargo
36/// metadata`.
37///
38/// For examples on how to use `PackageGraph`, see
39/// [the `examples` directory](https://github.com/guppy-rs/guppy/tree/main/guppy/examples)
40/// in this crate.
41#[derive(Clone, Debug)]
42pub struct PackageGraph {
43    // Source of truth data.
44    pub(super) dep_graph: Graph<PackageId, PackageLinkImpl, Directed, PackageIx>,
45    // The strongly connected components of the graph, computed on demand.
46    pub(super) sccs: OnceCell<Sccs<PackageIx>>,
47    // Feature graph, computed on demand.
48    pub(super) feature_graph: OnceCell<FeatureGraphImpl>,
49    // XXX Should this be in an Arc for quick cloning? Not clear how this would work with node
50    // filters though.
51    pub(super) data: PackageGraphData,
52}
53
54/// Per-package data for a PackageGraph instance.
55#[derive(Clone, Debug)]
56pub(super) struct PackageGraphData {
57    pub(super) packages: AHashMap<PackageId, PackageMetadataImpl>,
58    pub(super) workspace: WorkspaceImpl,
59}
60
61impl PackageGraph {
62    /// Executes the given `MetadataCommand` and constructs a `PackageGraph` from it.
63    pub fn from_command(command: &mut MetadataCommand) -> Result<Self, Error> {
64        command.build_graph()
65    }
66
67    /// Parses the given `Metadata` and constructs a `PackageGraph` from it.
68    pub fn from_metadata(metadata: CargoMetadata) -> Result<Self, Error> {
69        Self::build(metadata.0).map_err(|error| *error)
70    }
71
72    /// Constructs a package graph from the given JSON output of `cargo metadata`.
73    ///
74    /// Generally, `guppy` expects the `cargo metadata` command to be run with `--all-features`, so
75    /// that `guppy` has a full view of the dependency graph.
76    ///
77    /// For full functionality, `cargo metadata` should be run without `--no-deps`, so that `guppy`
78    /// knows about third-party crates and dependency edges. However, `guppy` supports a "light"
79    /// mode if `--no-deps` is run, in which case the following limitations will apply:
80    /// * dependency queries will not work
81    /// * there will be no information about non-workspace crates
82    pub fn from_json(json: impl AsRef<str>) -> Result<Self, Error> {
83        let metadata = CargoMetadata::parse_json(json)?;
84        Self::from_metadata(metadata)
85    }
86
87    /// Verifies internal invariants on this graph. Not part of the documented API.
88    #[doc(hidden)]
89    pub fn verify(&self) -> Result<(), Error> {
90        // Graph structure checks.
91        let node_count = self.dep_graph.node_count();
92        let package_count = self.data.packages.len();
93        if node_count != package_count {
94            return Err(Error::PackageGraphInternalError(format!(
95                "number of nodes = {} different from packages = {}",
96                node_count, package_count,
97            )));
98        }
99
100        // TODO: The dependency graph can have cyclic dev-dependencies. Add a check to ensure that
101        // the graph without any dev-only dependencies is acyclic.
102
103        let workspace = self.workspace();
104        let workspace_ids: HashSet<_> = workspace.member_ids().collect();
105
106        for metadata in self.packages() {
107            let package_id = metadata.id();
108
109            match metadata.source().workspace_path() {
110                Some(workspace_path) => {
111                    // This package is in the workspace, so the workspace should have information
112                    // about it.
113                    let metadata2 = workspace.member_by_path(workspace_path);
114                    let metadata2_id = metadata2.map(|metadata| metadata.id());
115                    if !matches!(metadata2_id, Ok(id) if id == package_id) {
116                        return Err(Error::PackageGraphInternalError(format!(
117                            "package {} has workspace path {:?} but query by path returned {:?}",
118                            package_id, workspace_path, metadata2_id,
119                        )));
120                    }
121
122                    let metadata3 = workspace.member_by_name(metadata.name());
123                    let metadata3_id = metadata3.map(|metadata| metadata.id());
124                    if !matches!(metadata3_id, Ok(id) if id == package_id) {
125                        return Err(Error::PackageGraphInternalError(format!(
126                            "package {} has name {}, but workspace query by name returned {:?}",
127                            package_id,
128                            metadata.name(),
129                            metadata3_id,
130                        )));
131                    }
132                }
133                None => {
134                    // This package is not in the workspace.
135                    if workspace_ids.contains(package_id) {
136                        return Err(Error::PackageGraphInternalError(format!(
137                            "package {} has no workspace path but is in workspace",
138                            package_id,
139                        )));
140                    }
141                }
142            }
143
144            for build_target in metadata.build_targets() {
145                match build_target.id() {
146                    BuildTargetId::Library | BuildTargetId::BuildScript => {
147                        // Ensure that the name is populated (this may panic if it isn't).
148                        build_target.name();
149                    }
150                    BuildTargetId::Binary(name)
151                    | BuildTargetId::Example(name)
152                    | BuildTargetId::Test(name)
153                    | BuildTargetId::Benchmark(name) => {
154                        if name != build_target.name() {
155                            return Err(Error::PackageGraphInternalError(format!(
156                                "package {} has build target name mismatch ({} != {})",
157                                package_id,
158                                name,
159                                build_target.name(),
160                            )));
161                        }
162                    }
163                }
164
165                let id_kind_mismatch = match build_target.id() {
166                    BuildTargetId::Library => match build_target.kind() {
167                        BuildTargetKind::LibraryOrExample(_) | BuildTargetKind::ProcMacro => false,
168                        BuildTargetKind::Binary => true,
169                    },
170                    BuildTargetId::Example(_) => match build_target.kind() {
171                        BuildTargetKind::LibraryOrExample(_) => false,
172                        BuildTargetKind::ProcMacro | BuildTargetKind::Binary => true,
173                    },
174                    BuildTargetId::BuildScript
175                    | BuildTargetId::Binary(_)
176                    | BuildTargetId::Test(_)
177                    | BuildTargetId::Benchmark(_) => match build_target.kind() {
178                        BuildTargetKind::LibraryOrExample(_) | BuildTargetKind::ProcMacro => true,
179                        BuildTargetKind::Binary => false,
180                    },
181                };
182
183                if id_kind_mismatch {
184                    return Err(Error::PackageGraphInternalError(format!(
185                        "package {} has build target id {:?}, which doesn't match kind {:?}",
186                        package_id,
187                        build_target.id(),
188                        build_target.kind(),
189                    )));
190                }
191            }
192
193            for link in self.dep_links_ixs_directed(metadata.package_ix(), Outgoing) {
194                let to = link.to();
195                let to_id = to.id();
196                let to_version = to.version();
197
198                // Two invariants:
199                // 1. At least one of the edges should be specified.
200                // 2. The specified package should match the version dependency.
201
202                let req = link.version_req();
203                // A requirement of "*" filters out pre-release versions with the semver crate,
204                // but cargo accepts them.
205                // See https://github.com/steveklabnik/semver/issues/98.
206                if !cargo_version_matches(req, to_version) {
207                    return Err(Error::PackageGraphInternalError(format!(
208                        "{} -> {}: version ({}) doesn't match requirement ({:?})",
209                        package_id, to_id, to_version, req,
210                    )));
211                }
212
213                let is_any = link.normal().is_present()
214                    || link.build().is_present()
215                    || link.dev().is_present();
216
217                if !is_any {
218                    return Err(Error::PackageGraphInternalError(format!(
219                        "{} -> {}: no edge info found",
220                        package_id, to_id,
221                    )));
222                }
223            }
224        }
225
226        // Construct and check the feature graph for internal consistency.
227        self.feature_graph().verify()?;
228
229        Ok(())
230    }
231
232    /// Returns information about the workspace.
233    pub fn workspace(&self) -> Workspace {
234        Workspace {
235            graph: self,
236            inner: &self.data.workspace,
237        }
238    }
239
240    /// Returns an iterator over all the package IDs in this graph.
241    pub fn package_ids(&self) -> impl ExactSizeIterator<Item = &PackageId> {
242        self.data.package_ids()
243    }
244
245    /// Returns an iterator over all the packages in this graph.
246    pub fn packages(&self) -> impl ExactSizeIterator<Item = PackageMetadata> {
247        self.data
248            .packages
249            .values()
250            .map(move |inner| PackageMetadata::new(self, inner))
251    }
252
253    /// Returns the metadata for the given package ID.
254    pub fn metadata(&self, package_id: &PackageId) -> Result<PackageMetadata, Error> {
255        let inner = self
256            .data
257            .metadata_impl(package_id)
258            .ok_or_else(|| Error::UnknownPackageId(package_id.clone()))?;
259        Ok(PackageMetadata::new(self, inner))
260    }
261
262    /// Returns the number of packages in this graph.
263    pub fn package_count(&self) -> usize {
264        // This can be obtained in two different ways: self.dep_graph.node_count() or
265        // self.data.packages.len(). verify() checks that they return the same results.
266        //
267        // Use this way for symmetry with link_count below (which can only be obtained through the
268        // graph).
269        self.dep_graph.node_count()
270    }
271
272    /// Returns the number of links in this graph.
273    pub fn link_count(&self) -> usize {
274        self.dep_graph.edge_count()
275    }
276
277    /// Creates a new cache for `depends_on` queries.
278    ///
279    /// The cache is optional but can speed up some queries.
280    pub fn new_depends_cache(&self) -> DependsCache {
281        DependsCache::new(self)
282    }
283
284    /// Returns true if `package_a` depends (directly or indirectly) on `package_b`.
285    ///
286    /// In other words, this returns true if `package_b` is a (possibly transitive) dependency of
287    /// `package_a`.
288    ///
289    /// This also returns true if `package_a` is the same as `package_b`.
290    ///
291    /// For repeated queries, consider using `new_depends_cache` to speed up queries.
292    pub fn depends_on(&self, package_a: &PackageId, package_b: &PackageId) -> Result<bool, Error> {
293        let mut depends_cache = self.new_depends_cache();
294        depends_cache.depends_on(package_a, package_b)
295    }
296
297    /// Returns true if `package_a` directly depends on `package_b`.
298    ///
299    /// In other words, this returns true if `package_b` is a direct dependency of `package_a`.
300    ///
301    /// This returns false if `package_a` is the same as `package_b`.
302    pub fn directly_depends_on(
303        &self,
304        package_a: &PackageId,
305        package_b: &PackageId,
306    ) -> Result<bool, Error> {
307        let a_ix = self.package_ix(package_a)?;
308        let b_ix = self.package_ix(package_b)?;
309        Ok(self.dep_graph.contains_edge(a_ix, b_ix))
310    }
311
312    /// Returns information about dependency cycles in this graph.
313    ///
314    /// For more information, see the documentation for `Cycles`.
315    pub fn cycles(&self) -> Cycles {
316        Cycles::new(self)
317    }
318
319    // For more traversals, see query.rs.
320
321    // ---
322    // Helper methods
323    // ---
324
325    fn dep_links_ixs_directed(
326        &self,
327        package_ix: NodeIndex<PackageIx>,
328        dir: Direction,
329    ) -> impl Iterator<Item = PackageLink<'_>> {
330        self.dep_graph
331            .edges_directed(package_ix, dir)
332            .map(move |edge| self.edge_ref_to_link(edge))
333    }
334
335    fn link_between_ixs(
336        &self,
337        from_ix: NodeIndex<PackageIx>,
338        to_ix: NodeIndex<PackageIx>,
339    ) -> Option<PackageLink<'_>> {
340        self.dep_graph
341            .find_edge(from_ix, to_ix)
342            .map(|edge_ix| self.edge_ix_to_link(edge_ix))
343    }
344
345    /// Constructs a map of strongly connected components for this graph.
346    pub(super) fn sccs(&self) -> &Sccs<PackageIx> {
347        self.sccs.get_or_init(|| {
348            let edge_filtered =
349                EdgeFiltered::from_fn(&self.dep_graph, |edge| !edge.weight().dev_only());
350            // Sort the entire graph without dev-only edges -- a correct graph would be cycle-free
351            // but we don't currently do a consistency check for this so handle cycles.
352            // TODO: should we check at construction time? or bubble up a warning somehow?
353            let topo = TopoWithCycles::new(&edge_filtered);
354
355            Sccs::new(&self.dep_graph, |scc| {
356                topo.sort_nodes(scc);
357            })
358        })
359    }
360
361    /// Invalidates internal caches. Primarily for testing.
362    #[doc(hidden)]
363    pub fn invalidate_caches(&mut self) {
364        self.sccs.take();
365        self.feature_graph.take();
366    }
367
368    /// Returns the inner dependency graph.
369    ///
370    /// Should this be exposed publicly? Not sure.
371    pub(super) fn dep_graph(&self) -> &Graph<PackageId, PackageLinkImpl, Directed, PackageIx> {
372        &self.dep_graph
373    }
374
375    /// Maps an edge reference to a dependency link.
376    pub(super) fn edge_ref_to_link<'g>(
377        &'g self,
378        edge: EdgeReference<'g, PackageLinkImpl, PackageIx>,
379    ) -> PackageLink<'g> {
380        PackageLink::new(
381            self,
382            edge.source(),
383            edge.target(),
384            edge.id(),
385            Some(edge.weight()),
386        )
387    }
388
389    /// Maps an edge index to a dependency link.
390    pub(super) fn edge_ix_to_link(&self, edge_ix: EdgeIndex<PackageIx>) -> PackageLink {
391        let (source_ix, target_ix) = self
392            .dep_graph
393            .edge_endpoints(edge_ix)
394            .expect("valid edge ix");
395        PackageLink::new(
396            self,
397            source_ix,
398            target_ix,
399            edge_ix,
400            self.dep_graph.edge_weight(edge_ix),
401        )
402    }
403
404    /// Maps an iterator of package IDs to their internal graph node indexes.
405    pub(super) fn package_ixs<'g, 'a, B>(
406        &'g self,
407        package_ids: impl IntoIterator<Item = &'a PackageId>,
408    ) -> Result<B, Error>
409    where
410        B: iter::FromIterator<NodeIndex<PackageIx>>,
411    {
412        package_ids
413            .into_iter()
414            .map(|package_id| self.package_ix(package_id))
415            .collect()
416    }
417
418    /// Maps a package ID to its internal graph node index, and returns an `UnknownPackageId` error
419    /// if the package isn't found.
420    pub(super) fn package_ix(&self, package_id: &PackageId) -> Result<NodeIndex<PackageIx>, Error> {
421        Ok(self.metadata(package_id)?.package_ix())
422    }
423}
424
425impl PackageGraphData {
426    /// Returns an iterator over all the package IDs in this graph.
427    pub fn package_ids(&self) -> impl ExactSizeIterator<Item = &PackageId> {
428        self.packages.keys()
429    }
430
431    // ---
432    // Helper methods
433    // ---
434
435    #[inline]
436    pub(super) fn metadata_impl(&self, package_id: &PackageId) -> Option<&PackageMetadataImpl> {
437        self.packages.get(package_id)
438    }
439}
440
441/// An optional cache used to speed up `depends_on` queries.
442///
443/// Created with `PackageGraph::new_depends_cache()`.
444#[derive(Clone, Debug)]
445pub struct DependsCache<'g> {
446    package_graph: &'g PackageGraph,
447    dfs_space: DfsSpace<NodeIndex<PackageIx>, FixedBitSet>,
448}
449
450impl<'g> DependsCache<'g> {
451    /// Creates a new cache for `depends_on` queries for this package graph.
452    ///
453    /// This holds a shared reference to the package graph. This is to ensure that the cache is
454    /// invalidated if the package graph is mutated.
455    pub fn new(package_graph: &'g PackageGraph) -> Self {
456        Self {
457            package_graph,
458            dfs_space: DfsSpace::new(&package_graph.dep_graph),
459        }
460    }
461
462    /// Returns true if `package_a` depends (directly or indirectly) on `package_b`.
463    ///
464    /// In other words, this returns true if `package_b` is a (possibly transitive) dependency of
465    /// `package_a`.
466    pub fn depends_on(
467        &mut self,
468        package_a: &PackageId,
469        package_b: &PackageId,
470    ) -> Result<bool, Error> {
471        let a_ix = self.package_graph.package_ix(package_a)?;
472        let b_ix = self.package_graph.package_ix(package_b)?;
473        Ok(has_path_connecting(
474            self.package_graph.dep_graph(),
475            a_ix,
476            b_ix,
477            Some(&mut self.dfs_space),
478        ))
479    }
480}
481
482/// Information about a workspace, parsed from metadata returned by `cargo metadata`.
483///
484/// For more about workspaces, see
485/// [Cargo Workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) in *The Rust
486/// Programming Language*.
487#[derive(Clone, Debug)]
488pub struct Workspace<'g> {
489    graph: &'g PackageGraph,
490    pub(super) inner: &'g WorkspaceImpl,
491}
492
493impl<'g> Workspace<'g> {
494    /// Returns the workspace root.
495    pub fn root(&self) -> &'g Utf8Path {
496        &self.inner.root
497    }
498
499    /// Returns the target directory in which output artifacts are stored.
500    pub fn target_directory(&self) -> &'g Utf8Path {
501        &self.inner.target_directory
502    }
503
504    /// Returns the number of packages in this workspace.
505    pub fn member_count(&self) -> usize {
506        self.inner.members_by_path.len()
507    }
508
509    /// Returns true if the workspace contains a package by the given name.
510    pub fn contains_name(&self, name: impl AsRef<str>) -> bool {
511        self.inner.members_by_name.contains_key(name.as_ref())
512    }
513
514    /// Returns true if the workspace contains a package by the given workspace path.
515    pub fn contains_path(&self, path: impl AsRef<Utf8Path>) -> bool {
516        self.inner.members_by_path.contains_key(path.as_ref())
517    }
518
519    /// Returns an iterator over package metadatas, sorted by the path they're in.
520    pub fn iter(&self) -> impl ExactSizeIterator<Item = PackageMetadata<'g>> {
521        self.iter_by_path().map(|(_, package)| package)
522    }
523
524    /// Returns an iterator over workspace paths and package metadatas, sorted by the path
525    /// they're in.
526    pub fn iter_by_path(
527        &self,
528    ) -> impl ExactSizeIterator<Item = (&'g Utf8Path, PackageMetadata<'g>)> {
529        let graph = self.graph;
530        self.inner.members_by_path.iter().map(move |(path, id)| {
531            (
532                path.as_path(),
533                graph.metadata(id).expect("valid package ID"),
534            )
535        })
536    }
537
538    /// Returns an iterator over workspace names and package metadatas, sorted by names.
539    pub fn iter_by_name(&self) -> impl ExactSizeIterator<Item = (&'g str, PackageMetadata<'g>)> {
540        let graph = self.graph;
541        self.inner
542            .members_by_name
543            .iter()
544            .map(move |(name, id)| (name.as_ref(), graph.metadata(id).expect("valid package ID")))
545    }
546
547    /// Returns an iterator over package IDs for workspace members. The package IDs will be returned
548    /// in the same order as `members`, sorted by the path they're in.
549    pub fn member_ids(&self) -> impl ExactSizeIterator<Item = &'g PackageId> {
550        self.inner.members_by_path.values()
551    }
552
553    /// Maps the given path to the corresponding workspace member.
554    ///
555    /// Returns an error if the path didn't match any workspace members.
556    pub fn member_by_path(&self, path: impl AsRef<Utf8Path>) -> Result<PackageMetadata<'g>, Error> {
557        let path = path.as_ref();
558        let id = self
559            .inner
560            .members_by_path
561            .get(path)
562            .ok_or_else(|| Error::UnknownWorkspacePath(path.to_path_buf()))?;
563        Ok(self.graph.metadata(id).expect("valid package ID"))
564    }
565
566    /// Maps the given paths to their corresponding workspace members, returning a new value of
567    /// the specified collection type (e.g. `Vec`).
568    ///
569    /// Returns an error if any of the paths were unknown.
570    pub fn members_by_paths<B>(
571        &self,
572        paths: impl IntoIterator<Item = impl AsRef<Utf8Path>>,
573    ) -> Result<B, Error>
574    where
575        B: FromIterator<PackageMetadata<'g>>,
576    {
577        paths
578            .into_iter()
579            .map(|path| self.member_by_path(path.as_ref()))
580            .collect()
581    }
582
583    /// Maps the given name to the corresponding workspace member.
584    ///
585    /// Returns an error if the name didn't match any workspace members.
586    pub fn member_by_name(&self, name: impl AsRef<str>) -> Result<PackageMetadata<'g>, Error> {
587        let name = name.as_ref();
588        let id = self
589            .inner
590            .members_by_name
591            .get(name)
592            .ok_or_else(|| Error::UnknownWorkspaceName(name.to_string()))?;
593        Ok(self.graph.metadata(id).expect("valid package ID"))
594    }
595
596    /// Maps the given names to their corresponding workspace members, returning a new value of
597    /// the specified collection type (e.g. `Vec`).
598    ///
599    /// Returns an error if any of the paths were unknown.
600    pub fn members_by_names<B>(
601        &self,
602        names: impl IntoIterator<Item = impl AsRef<str>>,
603    ) -> Result<B, Error>
604    where
605        B: FromIterator<PackageMetadata<'g>>,
606    {
607        names
608            .into_iter()
609            .map(|name| self.member_by_name(name.as_ref()))
610            .collect()
611    }
612
613    /// Returns the freeform metadata table for this workspace.
614    ///
615    /// This is the same as the `workspace.metadata` section of `Cargo.toml`. This section is
616    /// typically used by tools which would like to store workspace configuration in `Cargo.toml`.
617    pub fn metadata_table(&self) -> &'g JsonValue {
618        &self.inner.metadata_table
619    }
620}
621
622#[cfg(feature = "rayon1")]
623mod workspace_rayon {
624    use super::*;
625    use rayon::prelude::*;
626
627    /// These parallel iterators require the `rayon1` feature is enabled.
628    impl<'g> Workspace<'g> {
629        /// Returns a parallel iterator over package metadatas, sorted by workspace path.
630        ///
631        /// Requires the `rayon1` feature to be enabled.
632        pub fn par_iter(&self) -> impl ParallelIterator<Item = PackageMetadata<'g>> {
633            self.par_iter_by_path().map(|(_, package)| package)
634        }
635
636        /// Returns a parallel iterator over workspace paths and package metadatas, sorted by
637        /// workspace paths.
638        ///
639        /// Requires the `rayon1` feature to be enabled.
640        pub fn par_iter_by_path(
641            &self,
642        ) -> impl ParallelIterator<Item = (&'g Utf8Path, PackageMetadata<'g>)> {
643            let graph = self.graph;
644            self.inner
645                .members_by_path
646                .par_iter()
647                .map(move |(path, id)| {
648                    (
649                        path.as_path(),
650                        graph.metadata(id).expect("valid package ID"),
651                    )
652                })
653        }
654
655        /// Returns a parallel iterator over workspace names and package metadatas, sorted by
656        /// package names.
657        ///
658        /// Requires the `rayon1` feature to be enabled.
659        pub fn par_iter_by_name(
660            &self,
661        ) -> impl ParallelIterator<Item = (&'g str, PackageMetadata<'g>)> {
662            let graph = self.graph;
663            self.inner
664                .members_by_name
665                .par_iter()
666                .map(move |(name, id)| {
667                    (name.as_ref(), graph.metadata(id).expect("valid package ID"))
668                })
669        }
670    }
671}
672
673#[derive(Clone, Debug)]
674pub(super) struct WorkspaceImpl {
675    pub(super) root: Utf8PathBuf,
676    pub(super) target_directory: Utf8PathBuf,
677    pub(super) metadata_table: JsonValue,
678    // This is a BTreeMap to allow presenting data in sorted order.
679    pub(super) members_by_path: BTreeMap<Utf8PathBuf, PackageId>,
680    pub(super) members_by_name: BTreeMap<Box<str>, PackageId>,
681    // Cache for members by name (only used for proptests)
682    #[cfg(feature = "proptest1")]
683    pub(super) name_list: OnceCell<Vec<Box<str>>>,
684}
685
686/// Information about a specific package in a `PackageGraph`.
687///
688/// Most of the metadata is extracted from `Cargo.toml` files. See
689/// [the `Cargo.toml` reference](https://doc.rust-lang.org/cargo/reference/manifest.html) for more
690/// details.
691#[derive(Copy, Clone)]
692pub struct PackageMetadata<'g> {
693    graph: &'g PackageGraph,
694    inner: &'g PackageMetadataImpl,
695}
696
697impl fmt::Debug for PackageMetadata<'_> {
698    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
699        f.debug_struct("PackageMetadata")
700            .field("package_id", &self.id().repr())
701            .field("..", &"..")
702            .finish()
703    }
704}
705
706assert_covariant!(PackageMetadata);
707
708impl<'g> PackageMetadata<'g> {
709    pub(super) fn new(graph: &'g PackageGraph, inner: &'g PackageMetadataImpl) -> Self {
710        Self { graph, inner }
711    }
712
713    /// Returns the unique identifier for this package.
714    pub fn id(&self) -> &'g PackageId {
715        &self.graph.dep_graph[self.inner.package_ix]
716    }
717
718    /// Returns the package graph this `PackageMetadata` is derived from.
719    pub fn graph(&self) -> &'g PackageGraph {
720        self.graph
721    }
722
723    /// Creates a `PackageQuery` consisting of this package, in the given direction.
724    ///
725    /// The `PackageQuery` can be used to inspect dependencies in this graph.
726    pub fn to_package_query(&self, direction: DependencyDirection) -> PackageQuery<'g> {
727        self.graph
728            .query_from_parts(iter::once(self.inner.package_ix).collect(), direction)
729    }
730
731    /// Creates a `PackageSet` consisting of just this package.
732    pub fn to_package_set(&self) -> PackageSet<'g> {
733        let included: IxBitSet = iter::once(self.package_ix()).collect();
734        PackageSet::from_included(self.graph, included)
735    }
736
737    /// Creates a `FeatureSet` that consists of all features in the package that match the given
738    /// named filter.
739    pub fn to_feature_set(&self, features: impl FeatureFilter<'g>) -> FeatureSet<'g> {
740        self.to_package_set().to_feature_set(features)
741    }
742
743    // ---
744    // Dependency traversals
745    // ---
746
747    /// Returns `PackageLink` instances corresponding to the direct dependencies for this package in
748    /// the specified direction.
749    pub fn direct_links_directed(
750        &self,
751        direction: DependencyDirection,
752    ) -> impl Iterator<Item = PackageLink<'g>> + 'g {
753        self.direct_links_impl(direction.into())
754    }
755
756    /// Returns `PackageLink` instances corresponding to the direct dependencies for this package.
757    pub fn direct_links(&self) -> impl Iterator<Item = PackageLink<'g>> + 'g {
758        self.direct_links_impl(Outgoing)
759    }
760
761    /// Returns `PackageLink` instances corresponding to the packages that directly depend on this
762    /// one.
763    pub fn reverse_direct_links(&self) -> impl Iterator<Item = PackageLink<'g>> + 'g {
764        self.direct_links_impl(Incoming)
765    }
766
767    /// Returns the direct `PackageLink` between `self` and `other` in the specified direction:
768    /// * `Forward`: from `self` to `other`
769    /// * `Reverse`: from `other` to `self`
770    ///
771    /// Returns `None` if the direct link does not exist, or an error if `to` isn't found in
772    /// `self.graph()`.
773    pub fn link_between(
774        &self,
775        other: &PackageId,
776        direction: DependencyDirection,
777    ) -> Result<Option<PackageLink<'g>>, Error> {
778        self.link_between_impl(other, direction.into())
779    }
780
781    /// Returns the direct `PackageLink` from `self` to the specified package, or `None` if `self`
782    /// does not directly depend on the specified package.
783    ///
784    /// Returns an error if `to` isn't found in `self.graph()`.
785    pub fn link_to(&self, to: &PackageId) -> Result<Option<PackageLink<'g>>, Error> {
786        self.link_between_impl(to, Outgoing)
787    }
788
789    /// Returns the direct `PackageLink` from the specified package to `self`, or `None` if the
790    /// specified package does not directly depend on `self`.
791    ///
792    /// Returns an error if `from` isn't found in `self.graph()`.
793    pub fn link_from(&self, from: &PackageId) -> Result<Option<PackageLink<'g>>, Error> {
794        self.link_between_impl(from, Incoming)
795    }
796
797    // ---
798    // Package fields
799    // ---
800
801    /// Returns the name of this package.
802    ///
803    /// This is the same as the `name` field of `Cargo.toml`.
804    pub fn name(&self) -> &'g str {
805        &self.inner.name
806    }
807
808    /// Returns the version of this package as resolved by Cargo.
809    ///
810    /// This is the same as the `version` field of `Cargo.toml`.
811    pub fn version(&self) -> &'g Version {
812        &self.inner.version
813    }
814
815    /// Returns the authors of this package.
816    ///
817    /// This is the same as the `authors` field of `Cargo.toml`.
818    pub fn authors(&self) -> &'g [String] {
819        &self.inner.authors
820    }
821
822    /// Returns a short description for this package.
823    ///
824    /// This is the same as the `description` field of `Cargo.toml`.
825    pub fn description(&self) -> Option<&'g str> {
826        self.inner.description.as_ref().map(|x| x.as_ref())
827    }
828
829    /// Returns an SPDX 2.1 license expression for this package, if specified.
830    ///
831    /// This is the same as the `license` field of `Cargo.toml`. Note that `guppy` does not perform
832    /// any validation on this, though `crates.io` does if a crate is uploaded there.
833    pub fn license(&self) -> Option<&'g str> {
834        self.inner.license.as_ref().map(|x| x.as_ref())
835    }
836
837    /// Returns the path to a license file for this package, if specified.
838    ///
839    /// This is the same as the `license_file` field of `Cargo.toml`. It is typically only specified
840    /// for nonstandard licenses.
841    pub fn license_file(&self) -> Option<&'g Utf8Path> {
842        self.inner.license_file.as_ref().map(|path| path.as_ref())
843    }
844
845    /// Returns the source from which this package was retrieved.
846    ///
847    /// This may be the workspace path, an external path, or a registry like `crates.io`.
848    pub fn source(&self) -> PackageSource<'g> {
849        PackageSource::new(&self.inner.source)
850    }
851
852    /// Returns true if this package is in the workspace.
853    ///
854    /// For more detailed information, use `source()`.
855    pub fn in_workspace(&self) -> bool {
856        self.source().is_workspace()
857    }
858
859    /// Returns the full path to the `Cargo.toml` for this package.
860    ///
861    /// This is specific to the system that `cargo metadata` was run on.
862    pub fn manifest_path(&self) -> &'g Utf8Path {
863        &self.inner.manifest_path
864    }
865
866    /// Returns categories for this package.
867    ///
868    /// This is the same as the `categories` field of `Cargo.toml`. For packages on `crates.io`,
869    /// returned values are guaranteed to be
870    /// [valid category slugs](https://crates.io/category_slugs).
871    pub fn categories(&self) -> &'g [String] {
872        &self.inner.categories
873    }
874
875    /// Returns keywords for this package.
876    ///
877    /// This is the same as the `keywords` field of `Cargo.toml`.
878    pub fn keywords(&self) -> &'g [String] {
879        &self.inner.keywords
880    }
881
882    /// Returns a path to the README for this package, if specified.
883    ///
884    /// This is the same as the `readme` field of `Cargo.toml`. The path returned is relative to the
885    /// directory the `Cargo.toml` is in (i.e. relative to the parent of `self.manifest_path()`).
886    pub fn readme(&self) -> Option<&'g Utf8Path> {
887        self.inner.readme.as_ref().map(|path| path.as_ref())
888    }
889
890    /// Returns the source code repository for this package, if specified.
891    ///
892    /// This is the same as the `repository` field of `Cargo.toml`.
893    pub fn repository(&self) -> Option<&'g str> {
894        self.inner.repository.as_ref().map(|x| x.as_ref())
895    }
896
897    /// Returns the homepage for this package, if specified.
898    ///
899    /// This is the same as the `homepage` field of `Cargo.toml`.
900    pub fn homepage(&self) -> Option<&'g str> {
901        self.inner.homepage.as_ref().map(|x| x.as_ref())
902    }
903
904    /// Returns the documentation URL for this package, if specified.
905    ///
906    /// This is the same as the `homepage` field of `Cargo.toml`.
907    pub fn documentation(&self) -> Option<&'g str> {
908        self.inner.documentation.as_ref().map(|x| x.as_ref())
909    }
910
911    /// Returns the Rust edition this package is written against.
912    ///
913    /// This is the same as the `edition` field of `Cargo.toml`. It is `"2015"` by default.
914    pub fn edition(&self) -> &'g str {
915        &self.inner.edition
916    }
917
918    /// Returns the freeform metadata table for this package.
919    ///
920    /// This is the same as the `package.metadata` section of `Cargo.toml`. This section is
921    /// typically used by tools which would like to store package configuration in `Cargo.toml`.
922    pub fn metadata_table(&self) -> &'g JsonValue {
923        &self.inner.metadata_table
924    }
925
926    /// Returns the name of a native library this package links to, if specified.
927    ///
928    /// This is the same as the `links` field of `Cargo.toml`. See [The `links` Manifest
929    /// Key](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key) in
930    /// the Cargo book for more details.
931    pub fn links(&self) -> Option<&'g str> {
932        self.inner.links.as_ref().map(|x| x.as_ref())
933    }
934
935    /// Returns the registries to which this package may be published.
936    ///
937    /// This is derived from the `publish` field of `Cargo.toml`.
938    pub fn publish(&self) -> PackagePublish<'g> {
939        PackagePublish::new(&self.inner.publish)
940    }
941
942    /// Returns the binary that is run by default, if specified.
943    ///
944    /// Information about this binary can be queried using [the `build_target`
945    /// method](Self::build_target).
946    ///
947    /// This is derived from the `default-run` field of `Cargo.toml`.
948    pub fn default_run(&self) -> Option<&'g str> {
949        self.inner.default_run.as_ref().map(|x| x.as_ref())
950    }
951
952    /// Returns the minimum Rust compiler version, which should be able to compile the package, if
953    /// specified.
954    ///
955    /// This is the same as the `rust-version` field of `Cargo.toml`. For more, see [the
956    /// `rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
957    /// in the Cargo reference.
958    pub fn minimum_rust_version(&self) -> Option<&'g Version> {
959        self.inner.rust_version.as_ref()
960    }
961
962    /// Returns the minimum Rust compiler version, which should be able to compile the package, if
963    /// specified.
964    ///
965    /// Returned as a [`semver::VersionReq`]. This is actually not correct -- it is deprecated and
966    /// will go away in the next major version of guppy: use [`Self::minimum_rust_version`] instead.
967    ///
968    /// This is the same as the `rust-version` field of `Cargo.toml`. For more, see [the
969    /// `rust-version`
970    /// field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) in
971    /// the Cargo reference.
972    #[deprecated(
973        since = "0.17.1",
974        note = "use Self::rust_version instead, it returns a Version"
975    )]
976    pub fn rust_version(&self) -> Option<&'g VersionReq> {
977        self.inner.rust_version_req.as_ref()
978    }
979
980    /// Returns all the build targets for this package.
981    ///
982    /// For more, see [Cargo
983    /// Targets](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#cargo-targets)
984    /// in the Cargo reference.
985    pub fn build_targets(&self) -> impl Iterator<Item = BuildTarget<'g>> {
986        self.inner.build_targets.iter().map(BuildTarget::new)
987    }
988
989    /// Looks up a build target by identifier.
990    pub fn build_target(&self, id: &BuildTargetId<'_>) -> Option<BuildTarget<'g>> {
991        self.inner
992            .build_targets
993            .get_key_value(id.as_key())
994            .map(BuildTarget::new)
995    }
996
997    /// Returns true if this package is a procedural macro.
998    ///
999    /// For more about procedural macros, see [Procedural
1000    /// Macros](https://doc.rust-lang.org/reference/procedural-macros.html) in the Rust reference.
1001    pub fn is_proc_macro(&self) -> bool {
1002        match self.build_target(&BuildTargetId::Library) {
1003            Some(build_target) => matches!(build_target.kind(), BuildTargetKind::ProcMacro),
1004            None => false,
1005        }
1006    }
1007
1008    /// Returns true if this package has a build script.
1009    ///
1010    /// Cargo only follows build dependencies if a build script is set.
1011    ///
1012    /// For more about build scripts, see [Build
1013    /// Scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) in the Cargo
1014    /// reference.
1015    pub fn has_build_script(&self) -> bool {
1016        self.build_target(&BuildTargetId::BuildScript).is_some()
1017    }
1018
1019    /// Returns true if this package has a named feature named `default`.
1020    ///
1021    /// For more about default features, see [The `[features]`
1022    /// section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section) in
1023    /// the Cargo reference.
1024    pub fn has_default_feature(&self) -> bool {
1025        self.inner.has_default_feature
1026    }
1027
1028    /// Returns the `FeatureId` corresponding to the default feature.
1029    pub fn default_feature_id(&self) -> FeatureId<'g> {
1030        if self.inner.has_default_feature {
1031            FeatureId::new(self.id(), FeatureLabel::Named("default"))
1032        } else {
1033            FeatureId::base(self.id())
1034        }
1035    }
1036
1037    /// Returns the list of named features available for this package. This will include a feature
1038    /// named "default" if it is defined.
1039    ///
1040    /// A named feature is listed in the `[features]` section of `Cargo.toml`. For more, see
1041    /// [the reference](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section).
1042    pub fn named_features(&self) -> impl Iterator<Item = &'g str> + 'g {
1043        self.named_features_full()
1044            .map(|(_, named_feature, _)| named_feature)
1045    }
1046
1047    // ---
1048    // Helper methods
1049    // --
1050
1051    #[inline]
1052    pub(super) fn package_ix(&self) -> NodeIndex<PackageIx> {
1053        self.inner.package_ix
1054    }
1055
1056    fn link_between_impl(
1057        &self,
1058        other: &PackageId,
1059        dir: Direction,
1060    ) -> Result<Option<PackageLink<'g>>, Error> {
1061        let other_ix = self.graph.package_ix(other)?;
1062        match dir {
1063            Direction::Outgoing => Ok(self.graph.link_between_ixs(self.package_ix(), other_ix)),
1064            Direction::Incoming => Ok(self.graph.link_between_ixs(other_ix, self.package_ix())),
1065        }
1066    }
1067
1068    fn direct_links_impl(&self, dir: Direction) -> impl Iterator<Item = PackageLink<'g>> + 'g {
1069        self.graph.dep_links_ixs_directed(self.package_ix(), dir)
1070    }
1071
1072    pub(super) fn get_feature_idx(&self, label: FeatureLabel<'_>) -> Option<FeatureIndexInPackage> {
1073        match label {
1074            FeatureLabel::Base => Some(FeatureIndexInPackage::Base),
1075            FeatureLabel::OptionalDependency(dep_name) => self
1076                .inner
1077                .optional_deps
1078                .get_index_of(dep_name)
1079                .map(FeatureIndexInPackage::OptionalDependency),
1080            FeatureLabel::Named(feature_name) => self
1081                .inner
1082                .named_features
1083                .get_index_of(feature_name)
1084                .map(FeatureIndexInPackage::Named),
1085        }
1086    }
1087
1088    pub(super) fn feature_idx_to_label(&self, idx: FeatureIndexInPackage) -> FeatureLabel<'g> {
1089        match idx {
1090            FeatureIndexInPackage::Base => FeatureLabel::Base,
1091            FeatureIndexInPackage::OptionalDependency(idx) => FeatureLabel::OptionalDependency(
1092                self.inner
1093                    .optional_deps
1094                    .get_index(idx)
1095                    .expect("feature idx in optional_deps should be valid")
1096                    .as_ref(),
1097            ),
1098            FeatureIndexInPackage::Named(idx) => FeatureLabel::Named(
1099                self.inner
1100                    .named_features
1101                    .get_index(idx)
1102                    .expect("feature idx in optional_deps should be valid")
1103                    .0
1104                    .as_ref(),
1105            ),
1106        }
1107    }
1108
1109    #[allow(dead_code)]
1110    pub(super) fn all_feature_nodes(&self) -> impl Iterator<Item = FeatureNode> + 'g {
1111        let package_ix = self.package_ix();
1112        iter::once(FeatureNode::new(
1113            self.package_ix(),
1114            FeatureIndexInPackage::Base,
1115        ))
1116        .chain(
1117            (0..self.inner.named_features.len())
1118                .map(move |named_idx| FeatureNode::named_feature(package_ix, named_idx)),
1119        )
1120        .chain(
1121            (0..self.inner.optional_deps.len())
1122                .map(move |dep_idx| FeatureNode::optional_dep(package_ix, dep_idx)),
1123        )
1124    }
1125
1126    pub(super) fn named_features_full(
1127        &self,
1128    ) -> impl Iterator<Item = (FeatureIndexInPackage, &'g str, &'g [NamedFeatureDep])> + 'g {
1129        self.inner
1130            .named_features
1131            .iter()
1132            // IndexMap is documented to use indexes 0..n without holes, so this enumerate()
1133            // is correct.
1134            .enumerate()
1135            .map(|(idx, (feature, deps))| {
1136                (
1137                    FeatureIndexInPackage::Named(idx),
1138                    feature.as_ref(),
1139                    deps.as_slice(),
1140                )
1141            })
1142    }
1143
1144    pub(super) fn optional_deps_full(
1145        &self,
1146    ) -> impl Iterator<Item = (FeatureIndexInPackage, &'g str)> + 'g {
1147        self.inner
1148            .optional_deps
1149            .iter()
1150            // IndexMap is documented to use indexes 0..n without holes, so this enumerate()
1151            // is correct.
1152            .enumerate()
1153            .map(|(idx, dep_name)| {
1154                (
1155                    FeatureIndexInPackage::OptionalDependency(idx),
1156                    dep_name.as_ref(),
1157                )
1158            })
1159    }
1160}
1161
1162#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1163pub(crate) enum FeatureIndexInPackage {
1164    Base,
1165    OptionalDependency(usize),
1166    Named(usize),
1167}
1168
1169/// `PackageMetadata`'s `PartialEq` implementation uses pointer equality for the `PackageGraph`.
1170impl PartialEq for PackageMetadata<'_> {
1171    fn eq(&self, other: &Self) -> bool {
1172        // Checking for the same package ix is enough as each package is guaranteed to be a 1:1 map
1173        // with ixs.
1174        std::ptr::eq(self.graph, other.graph) && self.package_ix() == other.package_ix()
1175    }
1176}
1177
1178impl Eq for PackageMetadata<'_> {}
1179
1180#[derive(Clone, Debug)]
1181pub(crate) struct PackageMetadataImpl {
1182    // Implementation note: we use Box<str> and Box<Path> to save on memory use when possible.
1183
1184    // Fields extracted from the package.
1185    pub(super) name: String,
1186    pub(super) version: Version,
1187    pub(super) authors: Vec<String>,
1188    pub(super) description: Option<Box<str>>,
1189    pub(super) license: Option<Box<str>>,
1190    pub(super) license_file: Option<Box<Utf8Path>>,
1191    pub(super) manifest_path: Box<Utf8Path>,
1192    pub(super) categories: Vec<String>,
1193    pub(super) keywords: Vec<String>,
1194    pub(super) readme: Option<Box<Utf8Path>>,
1195    pub(super) repository: Option<Box<str>>,
1196    pub(super) homepage: Option<Box<str>>,
1197    pub(super) documentation: Option<Box<str>>,
1198    pub(super) edition: Box<str>,
1199    pub(super) metadata_table: JsonValue,
1200    pub(super) links: Option<Box<str>>,
1201    pub(super) publish: PackagePublishImpl,
1202    pub(super) default_run: Option<Box<str>>,
1203    pub(super) rust_version: Option<Version>,
1204    pub(super) rust_version_req: Option<VersionReq>,
1205    pub(super) named_features: IndexMap<Box<str>, SmallVec<[NamedFeatureDep; 4]>>,
1206    pub(super) optional_deps: IndexSet<Box<str>>,
1207
1208    // Other information.
1209    pub(super) package_ix: NodeIndex<PackageIx>,
1210    pub(super) source: PackageSourceImpl,
1211    pub(super) build_targets: BTreeMap<OwnedBuildTargetId, BuildTargetImpl>,
1212    pub(super) has_default_feature: bool,
1213}
1214
1215/// The source of a package.
1216///
1217/// This enum contains information about where a package is found, and whether it is inside or
1218/// outside the workspace.
1219#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1220pub enum PackageSource<'g> {
1221    /// This package is in the workspace.
1222    ///
1223    /// The path is relative to the workspace root.
1224    Workspace(&'g Utf8Path),
1225
1226    /// This package is a path dependency that isn't in the workspace.
1227    ///
1228    /// The path is relative to the workspace root.
1229    Path(&'g Utf8Path),
1230
1231    /// This package is an external dependency.
1232    ///
1233    /// * For packages retrieved from `crates.io`, the source is the string
1234    ///   `"registry+https://github.com/rust-lang/crates.io-index"`.
1235    /// * For packages retrieved from other registries, the source begins with `"registry+"`.
1236    /// * For packages retrieved from Git repositories, the source begins with `"git+"`.
1237    External(&'g str),
1238}
1239
1240assert_covariant!(PackageSource);
1241
1242impl<'g> PackageSource<'g> {
1243    /// The path to the crates.io registry.
1244    pub const CRATES_IO_REGISTRY: &'static str =
1245        "registry+https://github.com/rust-lang/crates.io-index";
1246
1247    pub(super) fn new(inner: &'g PackageSourceImpl) -> Self {
1248        match inner {
1249            PackageSourceImpl::Workspace(path) => PackageSource::Workspace(path),
1250            PackageSourceImpl::Path(path) => PackageSource::Path(path),
1251            PackageSourceImpl::CratesIo => PackageSource::External(Self::CRATES_IO_REGISTRY),
1252            PackageSourceImpl::External(source) => PackageSource::External(source),
1253        }
1254    }
1255
1256    /// Returns true if this package source represents a workspace.
1257    pub fn is_workspace(&self) -> bool {
1258        matches!(self, PackageSource::Workspace(_))
1259    }
1260
1261    /// Returns true if this package source represents a path dependency that isn't in the
1262    /// workspace.
1263    pub fn is_path(&self) -> bool {
1264        matches!(self, PackageSource::Path(_))
1265    }
1266
1267    /// Returns true if this package source represents an external dependency.
1268    pub fn is_external(&self) -> bool {
1269        matches!(self, PackageSource::External(_))
1270    }
1271
1272    /// Returns true if the source is `crates.io`.
1273    pub fn is_crates_io(&self) -> bool {
1274        matches!(self, PackageSource::External(Self::CRATES_IO_REGISTRY))
1275    }
1276
1277    /// Returns true if this package is a local dependency, i.e. either in the workspace or a local
1278    /// path.
1279    pub fn is_local(&self) -> bool {
1280        !self.is_external()
1281    }
1282
1283    /// Returns the path if this is a workspace dependency, or `None` if this is a non-workspace
1284    /// dependency.
1285    ///
1286    /// The path is relative to the workspace root.
1287    pub fn workspace_path(&self) -> Option<&'g Utf8Path> {
1288        match self {
1289            PackageSource::Workspace(path) => Some(path),
1290            _ => None,
1291        }
1292    }
1293
1294    /// Returns the local path if this is a local dependency, or `None` if it is an external
1295    /// dependency.
1296    ///
1297    /// The path is relative to the workspace root.
1298    pub fn local_path(&self) -> Option<&'g Utf8Path> {
1299        match self {
1300            PackageSource::Path(path) | PackageSource::Workspace(path) => Some(path),
1301            _ => None,
1302        }
1303    }
1304
1305    /// Returns the external source if this is an external dependency, or `None` if it is a local
1306    /// dependency.
1307    pub fn external_source(&self) -> Option<&'g str> {
1308        match self {
1309            PackageSource::External(source) => Some(source),
1310            _ => None,
1311        }
1312    }
1313
1314    /// Attempts to parse an external source.
1315    ///
1316    /// Returns `None` if the external dependency could not be recognized, or if it is a local
1317    /// dependency.
1318    ///
1319    /// For more about external sources, see the documentation for [`ExternalSource`](ExternalSource).
1320    pub fn parse_external(&self) -> Option<ExternalSource<'g>> {
1321        match self {
1322            PackageSource::External(source) => ExternalSource::new(source),
1323            _ => None,
1324        }
1325    }
1326}
1327
1328impl fmt::Display for PackageSource<'_> {
1329    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1330        match self {
1331            PackageSource::Workspace(path) => write!(f, "{}", path),
1332            PackageSource::Path(path) => write!(f, "{}", path),
1333            PackageSource::External(source) => write!(f, "{}", source),
1334        }
1335    }
1336}
1337
1338/// More information about an external source.
1339///
1340/// This provides information about whether an external dependency is a Git dependency or fetched
1341/// from a registry.
1342///
1343/// Returned by [`PackageSource::parse_external`](PackageSource::parse_external).
1344#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1345#[non_exhaustive]
1346pub enum ExternalSource<'g> {
1347    /// This is a registry source, e.g. `"registry+https://github.com/rust-lang/crates.io-index"`.
1348    ///
1349    /// The associated data is the part of the string after the initial `"registry+"`.
1350    ///
1351    /// # Examples
1352    ///
1353    /// ```
1354    /// use guppy::graph::ExternalSource;
1355    ///
1356    /// let source = "registry+https://github.com/rust-lang/crates.io-index";
1357    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1358    ///
1359    /// assert_eq!(
1360    ///     parsed,
1361    ///     ExternalSource::Registry("https://github.com/rust-lang/crates.io-index"),
1362    /// );
1363    /// ```
1364    Registry(&'g str),
1365
1366    /// This is a registry source that uses the [sparse registry protocol][sparse], e.g. `"sparse+https://index.crates.io"`.
1367    ///
1368    /// The associated data is the part of the string after the initial `"sparse+"`.
1369    ///
1370    /// # Examples
1371    ///
1372    /// ```
1373    /// use guppy::graph::ExternalSource;
1374    ///
1375    /// let source = "sparse+https://index.crates.io";
1376    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1377    ///
1378    /// assert_eq!(
1379    ///     parsed,
1380    ///     ExternalSource::Sparse("https://index.crates.io"),
1381    /// );
1382    /// ```
1383    ///
1384    /// [sparse]: https://doc.rust-lang.org/cargo/reference/registry-index.html#sparse-protocol
1385    Sparse(&'g str),
1386
1387    /// This is a Git source.
1388    ///
1389    /// An example of a Git source string is `"git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1390    /// In this case, the `Cargo.toml` would have contained:
1391    ///
1392    /// ```toml
1393    /// cargo = { git = "https://github.com/rust-lang/cargo.git", branch = "main" }
1394    /// ```
1395    ///
1396    /// and the `Cargo.lock` would have contained:
1397    ///
1398    /// ```toml
1399    /// [[package]]
1400    /// name = "cargo"
1401    /// version = "0.46.0"
1402    /// source = "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4
1403    /// dependencies = [ ... ]
1404    /// ```
1405    ///
1406    /// For more, see [Specifying dependencies from `git` repositories](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories)
1407    /// in the Cargo book.
1408    ///
1409    /// # Examples
1410    ///
1411    /// ```
1412    /// use guppy::graph::{ExternalSource, GitReq};
1413    ///
1414    /// // A branch source.
1415    /// let source = "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1416    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1417    ///
1418    /// assert_eq!(
1419    ///     parsed,
1420    ///     ExternalSource::Git {
1421    ///         repository: "https://github.com/rust-lang/cargo.git",
1422    ///         req: GitReq::Branch("main"),
1423    ///         resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1424    ///     }
1425    /// );
1426    ///
1427    /// // A tag source.
1428    /// let source = "git+https://github.com/rust-lang/cargo.git?tag=v0.46.0#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1429    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1430    ///
1431    /// assert_eq!(
1432    ///     parsed,
1433    ///     ExternalSource::Git {
1434    ///         repository: "https://github.com/rust-lang/cargo.git",
1435    ///         req: GitReq::Tag("v0.46.0"),
1436    ///         resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1437    ///     }
1438    /// );
1439    ///
1440    /// // A revision source.
1441    /// let source = "git+https://github.com/rust-lang/cargo.git?rev=0227f048fcb7c798026ede6cc20c92befc84c3a4#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1442    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1443    ///
1444    /// assert_eq!(
1445    ///     parsed,
1446    ///     ExternalSource::Git {
1447    ///         repository: "https://github.com/rust-lang/cargo.git",
1448    ///         req: GitReq::Rev("0227f048fcb7c798026ede6cc20c92befc84c3a4"),
1449    ///         resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1450    ///     }
1451    /// );
1452    ///
1453    /// // A default source.
1454    /// let source = "git+https://github.com/gyscos/zstd-rs.git#bc874a57298bdb500cdb5aeac5f23878b6480d0b";
1455    /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1456    ///
1457    /// assert_eq!(
1458    ///     parsed,
1459    ///     ExternalSource::Git {
1460    ///         repository: "https://github.com/gyscos/zstd-rs.git",
1461    ///         req: GitReq::Default,
1462    ///         resolved: "bc874a57298bdb500cdb5aeac5f23878b6480d0b",
1463    ///     }
1464    /// );
1465    /// ```
1466    Git {
1467        /// The repository for this Git source. For the above example, this would be
1468        /// `"https://github.com/rust-lang/cargo.git"`.
1469        repository: &'g str,
1470
1471        /// The revision requested in `Cargo.toml`. This may be a tag, a branch or a specific
1472        /// revision (commit hash).
1473        ///
1474        /// For the above example, `req` would be `GitSource::Branch("main")`.
1475        req: GitReq<'g>,
1476
1477        /// The resolved revision, as specified in `Cargo.lock`.
1478        ///
1479        /// For the above example, `resolved_hash` would be `"0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1480        ///
1481        /// This is always a commit hash, and if `req` is `GitReq::Rev` then it is expected
1482        /// to be the same hash. (However, this is not verified by guppy.)
1483        resolved: &'g str,
1484    },
1485}
1486
1487impl<'g> ExternalSource<'g> {
1488    /// The string `"registry+"`.
1489    ///
1490    /// Used for matching with the `Registry` variant.
1491    pub const REGISTRY_PLUS: &'static str = "registry+";
1492
1493    /// The string `"sparse+"`.
1494    ///
1495    /// Also used for matching with the `Sparse` variant.
1496    pub const SPARSE_PLUS: &'static str = "sparse+";
1497
1498    /// The string `"git+"`.
1499    ///
1500    /// Used for matching with the `Git` variant.
1501    pub const GIT_PLUS: &'static str = "git+";
1502
1503    /// The string `"?branch="`.
1504    ///
1505    /// Used for matching with the `Git` variant.
1506    pub const BRANCH_EQ: &'static str = "?branch=";
1507
1508    /// The string `"?tag="`.
1509    ///
1510    /// Used for matching with the `Git` variant.
1511    pub const TAG_EQ: &'static str = "?tag=";
1512
1513    /// The string `"?rev="`.
1514    ///
1515    /// Used for matching with the `Git` variant.
1516    pub const REV_EQ: &'static str = "?rev=";
1517
1518    /// The URL for the `crates.io` registry.
1519    ///
1520    /// This lacks the leading `"registry+`" that's part of the [`PackageSource`].
1521    pub const CRATES_IO_URL: &'static str = "https://github.com/rust-lang/crates.io-index";
1522
1523    /// Attempts to parse the given string as an external source.
1524    ///
1525    /// Returns `None` if the string could not be recognized as an external source.
1526    pub fn new(source: &'g str) -> Option<Self> {
1527        // We *could* pull in a URL parsing library, but Cargo's sources are so limited that it
1528        // seems like a waste to.
1529        if let Some(registry) = source.strip_prefix(Self::REGISTRY_PLUS) {
1530            // A registry source.
1531            Some(ExternalSource::Registry(registry))
1532        } else if let Some(sparse) = source.strip_prefix(Self::SPARSE_PLUS) {
1533            // A sparse registry source.
1534            Some(ExternalSource::Sparse(sparse))
1535        } else if let Some(rest) = source.strip_prefix(Self::GIT_PLUS) {
1536            // A Git source.
1537            // Look for a trailing #, which indicates the resolved revision.
1538            let (rest, resolved) = rest.rsplit_once('#')?;
1539
1540            let (repository, req) = if let Some(idx) = rest.find(Self::BRANCH_EQ) {
1541                (
1542                    &rest[..idx],
1543                    GitReq::Branch(&rest[idx + Self::BRANCH_EQ.len()..]),
1544                )
1545            } else if let Some(idx) = rest.find(Self::TAG_EQ) {
1546                (&rest[..idx], GitReq::Tag(&rest[idx + Self::TAG_EQ.len()..]))
1547            } else if let Some(idx) = rest.find(Self::REV_EQ) {
1548                (&rest[..idx], GitReq::Rev(&rest[idx + Self::TAG_EQ.len()..]))
1549            } else {
1550                (rest, GitReq::Default)
1551            };
1552
1553            Some(ExternalSource::Git {
1554                repository,
1555                req,
1556                resolved,
1557            })
1558        } else {
1559            None
1560        }
1561    }
1562}
1563
1564/// The `Display` implementation for `ExternalSource` returns the string it was constructed from.
1565///
1566/// # Examples
1567///
1568/// ```
1569/// use guppy::graph::{ExternalSource, GitReq};
1570///
1571/// let source = ExternalSource::Git {
1572///     repository: "https://github.com/rust-lang/cargo.git",
1573///     req: GitReq::Branch("main"),
1574///     resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1575/// };
1576///
1577/// assert_eq!(
1578///     format!("{}", source),
1579///     "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4",
1580/// );
1581/// ```
1582impl fmt::Display for ExternalSource<'_> {
1583    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1584        match self {
1585            ExternalSource::Registry(url) => write!(f, "{}{}", Self::REGISTRY_PLUS, url),
1586            ExternalSource::Sparse(url) => write!(f, "{}{}", Self::SPARSE_PLUS, url),
1587            ExternalSource::Git {
1588                repository,
1589                req,
1590                resolved,
1591            } => {
1592                write!(f, "{}{}", Self::GIT_PLUS, repository)?;
1593                match req {
1594                    GitReq::Branch(branch) => write!(f, "{}{}", Self::BRANCH_EQ, branch)?,
1595                    GitReq::Tag(tag) => write!(f, "{}{}", Self::TAG_EQ, tag)?,
1596                    GitReq::Rev(rev) => write!(f, "{}{}", Self::REV_EQ, rev)?,
1597                    GitReq::Default => {}
1598                };
1599                write!(f, "#{}", resolved)
1600            }
1601        }
1602    }
1603}
1604
1605/// A `Cargo.toml` specification for a Git branch, tag, or revision.
1606///
1607/// For more, including examples, see the documentation for [`ExternalSource::Git`](ExternalSource::Git).
1608#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1609#[non_exhaustive]
1610pub enum GitReq<'g> {
1611    /// A branch, e.g. `"main"`.
1612    ///
1613    /// This is specified in `Cargo.toml` as:
1614    ///
1615    /// ```toml
1616    /// [dependencies]
1617    /// cargo = { git = "...", branch = "main" }
1618    /// ```
1619    Branch(&'g str),
1620
1621    /// A tag, e.g. `"guppy-0.5.0"`.
1622    ///
1623    /// This is specified in `Cargo.toml` as:
1624    ///
1625    /// ```toml
1626    /// [dependencies]
1627    /// guppy = { git = "...", tag = "guppy-0.5.0" }
1628    /// ```
1629    Tag(&'g str),
1630
1631    /// A revision (commit hash), e.g. `"0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1632    ///
1633    /// This is specified in `Cargo.toml` as:
1634    ///
1635    /// ```toml
1636    /// [dependencies]
1637    /// cargo = { git = "...", rev = "0227f048fcb7c798026ede6cc20c92befc84c3a4" }
1638    /// ```
1639    Rev(&'g str),
1640
1641    /// Not specified in `Cargo.toml`. Cargo treats this as the main branch by default.
1642    ///
1643    /// ```toml
1644    /// [dependencies]
1645    /// cargo = { git = "..." }
1646    /// ```
1647    Default,
1648}
1649
1650/// Internal representation of the source of a package.
1651#[derive(Clone, Debug, PartialEq, Eq)]
1652pub(super) enum PackageSourceImpl {
1653    Workspace(Box<Utf8Path>),
1654    Path(Box<Utf8Path>),
1655    // Special, common case.
1656    CratesIo,
1657    External(Box<str>),
1658}
1659
1660/// Locations that a package can be published to.
1661///
1662/// Returned by [`PackageMetadata::publish`].
1663#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1664#[non_exhaustive]
1665pub enum PackagePublish<'g> {
1666    /// Publication of this package is unrestricted.
1667    Unrestricted,
1668
1669    /// This package can only be published to the listed [package registry].
1670    ///
1671    /// If the list is empty, this package cannot be published to any registries.
1672    ///
1673    /// [package registry]: https://doc.rust-lang.org/cargo/reference/registries.html
1674    Registries(&'g [String]),
1675}
1676
1677// TODO: implement PartialOrd/Ord for these as well using lattice rules
1678
1679assert_covariant!(PackagePublish);
1680
1681impl<'g> PackagePublish<'g> {
1682    pub(super) fn new(inner: &'g PackagePublishImpl) -> Self {
1683        match inner {
1684            PackagePublishImpl::Unrestricted => PackagePublish::Unrestricted,
1685            PackagePublishImpl::Registries(registries) => PackagePublish::Registries(registries),
1686        }
1687    }
1688
1689    /// The string `"crates-io"`, indicating that a package can be published to
1690    /// [crates.io](https://crates.io/).
1691    pub const CRATES_IO: &'static str = "crates-io";
1692
1693    /// Returns true if this package can be published to any package registry.
1694    ///
1695    /// # Examples
1696    ///
1697    /// ```
1698    /// use guppy::graph::PackagePublish;
1699    ///
1700    /// assert!(PackagePublish::Unrestricted.is_unrestricted());
1701    /// assert!(!PackagePublish::Registries(&[PackagePublish::CRATES_IO.to_owned()]).is_unrestricted());
1702    /// assert!(!PackagePublish::Registries(&[]).is_unrestricted());
1703    /// ```
1704    pub fn is_unrestricted(&self) -> bool {
1705        matches!(self, PackagePublish::Unrestricted)
1706    }
1707
1708    /// Returns true if a package can be published to the given package registry.
1709    ///
1710    /// # Examples
1711    ///
1712    /// ```
1713    /// use guppy::graph::PackagePublish;
1714    ///
1715    /// // Unrestricted means this package can be published to any registry.
1716    /// assert!(PackagePublish::Unrestricted.can_publish_to(PackagePublish::CRATES_IO));
1717    /// assert!(PackagePublish::Unrestricted.can_publish_to("my-registry"));
1718    ///
1719    /// // Publish to specific registries but not others.
1720    /// let crates_io = &[PackagePublish::CRATES_IO.to_owned()];
1721    /// let crates_io_publish = PackagePublish::Registries(crates_io);
1722    /// assert!(crates_io_publish.can_publish_to(PackagePublish::CRATES_IO));
1723    /// assert!(!crates_io_publish.can_publish_to("my-registry"));
1724    ///
1725    /// // Cannot publish to any registries.
1726    /// assert!(!PackagePublish::Registries(&[]).can_publish_to(PackagePublish::CRATES_IO));
1727    /// ```
1728    pub fn can_publish_to(&self, registry: impl AsRef<str>) -> bool {
1729        let registry = registry.as_ref();
1730        match self {
1731            PackagePublish::Unrestricted => true,
1732            PackagePublish::Registries(registries) => registries.iter().any(|r| r == registry),
1733        }
1734    }
1735
1736    /// Returns true if a package can be published to crates.io.
1737    pub fn can_publish_to_crates_io(&self) -> bool {
1738        self.can_publish_to(Self::CRATES_IO)
1739    }
1740
1741    /// Returns true if a package cannot be published to any registries.
1742    ///
1743    /// # Examples
1744    ///
1745    /// ```
1746    /// use guppy::graph::PackagePublish;
1747    ///
1748    /// assert!(!PackagePublish::Unrestricted.is_never());
1749    /// assert!(!PackagePublish::Registries(&[PackagePublish::CRATES_IO.to_owned()]).is_never());
1750    /// assert!(PackagePublish::Registries(&[]).is_never());
1751    /// ```
1752    pub fn is_never(&self) -> bool {
1753        match self {
1754            PackagePublish::Unrestricted => false,
1755            PackagePublish::Registries(registries) => registries.is_empty(),
1756        }
1757    }
1758}
1759
1760/// Internal representation of PackagePublish.
1761#[derive(Clone, Debug)]
1762pub(super) enum PackagePublishImpl {
1763    Unrestricted,
1764    Registries(Box<[String]>),
1765}
1766
1767/// Represents a dependency from one package to another.
1768///
1769/// This struct contains information about:
1770/// * whether this dependency was renamed in the context of this crate.
1771/// * if this is a normal, dev and/or build dependency.
1772/// * platform-specific information about required, optional and status
1773#[derive(Copy, Clone, Debug)]
1774pub struct PackageLink<'g> {
1775    graph: &'g PackageGraph,
1776    from: &'g PackageMetadataImpl,
1777    to: &'g PackageMetadataImpl,
1778    edge_ix: EdgeIndex<PackageIx>,
1779    inner: &'g PackageLinkImpl,
1780}
1781
1782assert_covariant!(PackageLink);
1783
1784impl<'g> PackageLink<'g> {
1785    pub(super) fn new(
1786        graph: &'g PackageGraph,
1787        source_ix: NodeIndex<PackageIx>,
1788        target_ix: NodeIndex<PackageIx>,
1789        edge_ix: EdgeIndex<PackageIx>,
1790        inner: Option<&'g PackageLinkImpl>,
1791    ) -> Self {
1792        let from = graph
1793            .data
1794            .metadata_impl(&graph.dep_graph[source_ix])
1795            .expect("'from' should have associated metadata");
1796        let to = graph
1797            .data
1798            .metadata_impl(&graph.dep_graph[target_ix])
1799            .expect("'to' should have associated metadata");
1800        Self {
1801            graph,
1802            from,
1803            to,
1804            edge_ix,
1805            inner: inner.unwrap_or_else(|| &graph.dep_graph[edge_ix]),
1806        }
1807    }
1808
1809    /// Returns the package which depends on the `to` package.
1810    pub fn from(&self) -> PackageMetadata<'g> {
1811        PackageMetadata::new(self.graph, self.from)
1812    }
1813
1814    /// Returns the package which is depended on by the `from` package.
1815    pub fn to(&self) -> PackageMetadata<'g> {
1816        PackageMetadata::new(self.graph, self.to)
1817    }
1818
1819    /// Returns the endpoints as a pair of packages `(from, to)`.
1820    pub fn endpoints(&self) -> (PackageMetadata<'g>, PackageMetadata<'g>) {
1821        (self.from(), self.to())
1822    }
1823
1824    /// Returns the name for this dependency edge. This can be affected by a crate rename.
1825    pub fn dep_name(&self) -> &'g str {
1826        &self.inner.dep_name
1827    }
1828
1829    /// Returns the resolved name for this dependency edge. This may involve renaming the crate and
1830    /// replacing - with _.
1831    pub fn resolved_name(&self) -> &'g str {
1832        &self.inner.resolved_name
1833    }
1834
1835    /// Returns the semver requirements specified for this dependency.
1836    ///
1837    /// To get the resolved version, see the `to` field of the `PackageLink` this was part of.
1838    ///
1839    /// ## Notes
1840    ///
1841    /// A dependency can be requested multiple times, possibly with different version requirements,
1842    /// even if they all end up resolving to the same version. `version_req` will return any of
1843    /// those requirements.
1844    ///
1845    /// See [Specifying Dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies)
1846    /// in the Cargo reference for more details.
1847    pub fn version_req(&self) -> &'g VersionReq {
1848        &self.inner.version_req
1849    }
1850
1851    /// Returns details about this dependency from the `[dependencies]` section.
1852    pub fn normal(&self) -> DependencyReq<'g> {
1853        DependencyReq {
1854            inner: &self.inner.normal,
1855        }
1856    }
1857
1858    /// Returns details about this dependency from the `[build-dependencies]` section.
1859    pub fn build(&self) -> DependencyReq<'g> {
1860        DependencyReq {
1861            inner: &self.inner.build,
1862        }
1863    }
1864
1865    /// Returns details about this dependency from the `[dev-dependencies]` section.
1866    pub fn dev(&self) -> DependencyReq<'g> {
1867        DependencyReq {
1868            inner: &self.inner.dev,
1869        }
1870    }
1871
1872    /// Returns details about this dependency from the section specified by the given dependency
1873    /// kind.
1874    pub fn req_for_kind(&self, kind: DependencyKind) -> DependencyReq<'g> {
1875        match kind {
1876            DependencyKind::Normal => self.normal(),
1877            DependencyKind::Development => self.dev(),
1878            DependencyKind::Build => self.build(),
1879        }
1880    }
1881
1882    /// Return true if this edge is dev-only, i.e. code from this edge will not be included in
1883    /// normal builds.
1884    pub fn dev_only(&self) -> bool {
1885        self.inner.dev_only()
1886    }
1887
1888    // ---
1889    // Helper methods
1890    // ---
1891
1892    /// Returns the edge index.
1893    #[allow(dead_code)]
1894    pub(super) fn edge_ix(&self) -> EdgeIndex<PackageIx> {
1895        self.edge_ix
1896    }
1897
1898    /// Returns (source, target, edge) as a triple of pointers. Useful for testing.
1899    #[doc(hidden)]
1900    pub fn as_inner_ptrs(&self) -> PackageLinkPtrs {
1901        PackageLinkPtrs {
1902            from: self.from,
1903            to: self.to,
1904            inner: self.inner,
1905        }
1906    }
1907}
1908
1909/// An opaque identifier for a PackageLink's pointers. Used for tests.
1910#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
1911#[doc(hidden)]
1912pub struct PackageLinkPtrs {
1913    from: *const PackageMetadataImpl,
1914    to: *const PackageMetadataImpl,
1915    inner: *const PackageLinkImpl,
1916}
1917
1918#[derive(Clone, Debug)]
1919pub(crate) struct PackageLinkImpl {
1920    pub(super) dep_name: String,
1921    pub(super) resolved_name: String,
1922    pub(super) version_req: VersionReq,
1923    pub(super) normal: DependencyReqImpl,
1924    pub(super) build: DependencyReqImpl,
1925    pub(super) dev: DependencyReqImpl,
1926}
1927
1928impl PackageLinkImpl {
1929    #[inline]
1930    fn dev_only(&self) -> bool {
1931        self.normal.enabled().is_never() && self.build.enabled().is_never()
1932    }
1933}
1934
1935/// Information about a specific kind of dependency (normal, build or dev) from a package to another
1936/// package.
1937///
1938/// Usually found within the context of a [`PackageLink`](struct.PackageLink.html).
1939#[derive(Clone, Debug)]
1940pub struct DependencyReq<'g> {
1941    pub(super) inner: &'g DependencyReqImpl,
1942}
1943
1944impl<'g> DependencyReq<'g> {
1945    /// Returns true if there is at least one `Cargo.toml` entry corresponding to this requirement.
1946    ///
1947    /// For example, if this dependency is specified in the `[dev-dependencies]` section,
1948    /// `edge.dev().is_present()` will return true.
1949    pub fn is_present(&self) -> bool {
1950        !self.inner.enabled().is_never()
1951    }
1952
1953    /// Returns the enabled status of this dependency.
1954    ///
1955    /// `status` is the union of `default_features` and `no_default_features`.
1956    ///
1957    /// See the documentation for `EnabledStatus` for more.
1958    pub fn status(&self) -> EnabledStatus<'g> {
1959        self.inner.enabled()
1960    }
1961
1962    /// Returns the enabled status of this dependency when `default-features = true`.
1963    ///
1964    /// See the documentation for `EnabledStatus` for more.
1965    pub fn default_features(&self) -> EnabledStatus<'g> {
1966        self.inner.default_features()
1967    }
1968
1969    /// Returns the enabled status of this dependency when `default-features = false`.
1970    ///
1971    /// This is generally less useful than `status` or `default_features`, but is provided for
1972    /// completeness.
1973    ///
1974    /// See the documentation for `EnabledStatus` for more.
1975    pub fn no_default_features(&self) -> EnabledStatus<'g> {
1976        self.inner.no_default_features()
1977    }
1978
1979    /// Returns a list of all features possibly enabled by this dependency. This includes features
1980    /// that are only turned on if the dependency is optional, or features enabled by inactive
1981    /// platforms.
1982    pub fn features(&self) -> impl Iterator<Item = &'g str> {
1983        self.inner.all_features()
1984    }
1985
1986    /// Returns the enabled status of this feature.
1987    ///
1988    /// Note that as of Rust 1.42, the default feature resolver behaves in potentially surprising
1989    /// ways. See the [Cargo
1990    /// reference](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#features) for
1991    /// more.
1992    ///
1993    /// See the documentation for `EnabledStatus` for more.
1994    pub fn feature_status(&self, feature: &str) -> EnabledStatus<'g> {
1995        self.inner.feature_status(feature)
1996    }
1997}
1998
1999/// Whether a dependency or feature is required, optional, or disabled.
2000///
2001/// Returned by the methods on `DependencyMetadata`.
2002///
2003/// ## Examples
2004///
2005/// ```toml
2006/// [dependencies]
2007/// once_cell = "1"
2008/// ```
2009///
2010/// The dependency and default features are *required* on all platforms.
2011///
2012/// ```toml
2013/// [dependencies]
2014/// once_cell = { version = "1", optional = true }
2015/// ```
2016///
2017/// The dependency and default features are *optional* on all platforms.
2018///
2019/// ```toml
2020/// [target.'cfg(windows)'.dependencies]
2021/// once_cell = { version = "1", optional = true }
2022/// ```
2023///
2024/// The result is platform-dependent. On Windows, the dependency and default features are both
2025/// *optional*. On non-Windows platforms, the dependency and default features are *disabled*.
2026///
2027/// ```toml
2028/// [dependencies]
2029/// once_cell = { version = "1", optional = true }
2030///
2031/// [target.'cfg(windows)'.dependencies]
2032/// once_cell = { version = "1", optional = false, default-features = false }
2033/// ```
2034///
2035/// The result is platform-dependent. On Windows, the dependency is *mandatory* and default features
2036/// are *optional* (i.e. enabled if the `once_cell` feature is turned on).
2037///
2038/// On Unix platforms, the dependency and default features are both *optional*.
2039#[derive(Copy, Clone, Debug)]
2040pub struct EnabledStatus<'g> {
2041    required: PlatformStatus<'g>,
2042    optional: PlatformStatus<'g>,
2043}
2044
2045assert_covariant!(EnabledStatus);
2046
2047impl<'g> EnabledStatus<'g> {
2048    pub(super) fn new(required: &'g PlatformStatusImpl, optional: &'g PlatformStatusImpl) -> Self {
2049        Self {
2050            required: PlatformStatus::new(required),
2051            optional: PlatformStatus::new(optional),
2052        }
2053    }
2054
2055    /// Returns true if this dependency is never enabled on any platform.
2056    pub fn is_never(&self) -> bool {
2057        self.required.is_never() && self.optional.is_never()
2058    }
2059
2060    /// Evaluates whether this dependency is required on the given platform spec.
2061    ///
2062    /// Returns `Unknown` if the result was unknown, which may happen if evaluating against an
2063    /// individual platform and its target features are unknown.
2064    pub fn required_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
2065        self.required.enabled_on(platform_spec)
2066    }
2067
2068    /// Evaluates whether this dependency is enabled (required or optional) on the given platform
2069    /// spec.
2070    ///
2071    /// Returns `Unknown` if the result was unknown, which may happen if evaluating against an
2072    /// individual platform and its target features are unknown.
2073    pub fn enabled_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
2074        let required = self.required.enabled_on(platform_spec);
2075        let optional = self.optional.enabled_on(platform_spec);
2076
2077        required | optional
2078    }
2079
2080    /// Returns the `PlatformStatus` corresponding to whether this dependency is required.
2081    pub fn required_status(&self) -> PlatformStatus<'g> {
2082        self.required
2083    }
2084
2085    /// Returns the `PlatformStatus` corresponding to whether this dependency is optional.
2086    pub fn optional_status(&self) -> PlatformStatus<'g> {
2087        self.optional
2088    }
2089}
2090
2091#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
2092pub(super) enum NamedFeatureDep {
2093    NamedFeature(Box<str>),
2094    OptionalDependency(Box<str>),
2095    DependencyNamedFeature {
2096        dep_name: Box<str>,
2097        feature: Box<str>,
2098        weak: bool,
2099    },
2100}
2101
2102impl NamedFeatureDep {
2103    #[inline]
2104    pub(super) fn named_feature(feature_name: impl Into<String>) -> Self {
2105        Self::NamedFeature(feature_name.into().into_boxed_str())
2106    }
2107
2108    #[inline]
2109    pub(super) fn optional_dependency(dep_name: impl Into<String>) -> Self {
2110        Self::OptionalDependency(dep_name.into().into_boxed_str())
2111    }
2112
2113    #[inline]
2114    pub(super) fn dep_named_feature(
2115        dep_name: impl Into<String>,
2116        feature: impl Into<String>,
2117        weak: bool,
2118    ) -> Self {
2119        Self::DependencyNamedFeature {
2120            dep_name: dep_name.into().into_boxed_str(),
2121            feature: feature.into().into_boxed_str(),
2122            weak,
2123        }
2124    }
2125}
2126
2127impl fmt::Display for NamedFeatureDep {
2128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2129        match self {
2130            Self::NamedFeature(feature) => write!(f, "{}", feature),
2131            Self::OptionalDependency(dep_name) => write!(f, "dep:{}", dep_name),
2132            Self::DependencyNamedFeature {
2133                dep_name,
2134                feature,
2135                weak,
2136            } => {
2137                write!(
2138                    f,
2139                    "{}{}/{}",
2140                    dep_name,
2141                    if *weak { "?" } else { "" },
2142                    feature
2143                )
2144            }
2145        }
2146    }
2147}
2148
2149/// Information about dependency requirements.
2150#[derive(Clone, Debug, Default)]
2151pub(super) struct DependencyReqImpl {
2152    pub(super) required: DepRequiredOrOptional,
2153    pub(super) optional: DepRequiredOrOptional,
2154}
2155
2156impl DependencyReqImpl {
2157    fn all_features(&self) -> impl Iterator<Item = &str> {
2158        self.required
2159            .all_features()
2160            .chain(self.optional.all_features())
2161    }
2162
2163    pub(super) fn enabled(&self) -> EnabledStatus {
2164        self.make_status(|req_impl| &req_impl.build_if)
2165    }
2166
2167    pub(super) fn default_features(&self) -> EnabledStatus {
2168        self.make_status(|req_impl| &req_impl.default_features_if)
2169    }
2170
2171    pub(super) fn no_default_features(&self) -> EnabledStatus {
2172        self.make_status(|req_impl| &req_impl.no_default_features_if)
2173    }
2174
2175    pub(super) fn feature_status(&self, feature: &str) -> EnabledStatus {
2176        // This PlatformStatusImpl in static memory is so that the lifetimes work out.
2177        static DEFAULT_STATUS: PlatformStatusImpl = PlatformStatusImpl::Specs(Vec::new());
2178
2179        self.make_status(|req_impl| {
2180            req_impl
2181                .feature_targets
2182                .get(feature)
2183                .unwrap_or(&DEFAULT_STATUS)
2184        })
2185    }
2186
2187    fn make_status(
2188        &self,
2189        pred_fn: impl Fn(&DepRequiredOrOptional) -> &PlatformStatusImpl,
2190    ) -> EnabledStatus {
2191        EnabledStatus::new(pred_fn(&self.required), pred_fn(&self.optional))
2192    }
2193}
2194
2195/// Information about dependency requirements, scoped to either the dependency being required or
2196/// optional.
2197#[derive(Clone, Debug, Default)]
2198pub(super) struct DepRequiredOrOptional {
2199    pub(super) build_if: PlatformStatusImpl,
2200    pub(super) default_features_if: PlatformStatusImpl,
2201    pub(super) no_default_features_if: PlatformStatusImpl,
2202    pub(super) feature_targets: BTreeMap<String, PlatformStatusImpl>,
2203}
2204
2205impl DepRequiredOrOptional {
2206    pub(super) fn all_features(&self) -> impl Iterator<Item = &str> {
2207        self.feature_targets.keys().map(|s| s.as_str())
2208    }
2209}