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}