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