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