Skip to main content

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