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).collect(), 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 /// This is the same as the `rust-version` field of `Cargo.toml`. For more, see [the
993 /// `rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
994 /// in the Cargo reference.
995 pub fn minimum_rust_version(&self) -> Option<&'g Version> {
996 self.inner.rust_version.as_ref()
997 }
998
999 /// Returns the minimum Rust compiler version, which should be able to compile the package, if
1000 /// specified.
1001 ///
1002 /// Returned as a [`semver::VersionReq`]. This is actually not correct -- it is deprecated and
1003 /// will go away in the next major version of guppy: use [`Self::minimum_rust_version`] instead.
1004 ///
1005 /// This is the same as the `rust-version` field of `Cargo.toml`. For more, see [the
1006 /// `rust-version`
1007 /// field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) in
1008 /// the Cargo reference.
1009 #[deprecated(
1010 since = "0.17.1",
1011 note = "use Self::rust_version instead, it returns a Version"
1012 )]
1013 pub fn rust_version(&self) -> Option<&'g VersionReq> {
1014 self.inner.rust_version_req.as_ref()
1015 }
1016
1017 /// Returns all the build targets for this package.
1018 ///
1019 /// For more, see [Cargo
1020 /// Targets](https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html#cargo-targets)
1021 /// in the Cargo reference.
1022 pub fn build_targets(&self) -> impl Iterator<Item = BuildTarget<'g>> + use<'g> {
1023 self.inner.build_targets.iter().map(BuildTarget::new)
1024 }
1025
1026 /// Looks up a build target by identifier.
1027 pub fn build_target(&self, id: &BuildTargetId<'_>) -> Option<BuildTarget<'g>> {
1028 self.inner
1029 .build_targets
1030 .get_key_value(id.as_key())
1031 .map(BuildTarget::new)
1032 }
1033
1034 /// Returns true if this package is a procedural macro.
1035 ///
1036 /// For more about procedural macros, see [Procedural
1037 /// Macros](https://doc.rust-lang.org/reference/procedural-macros.html) in the Rust reference.
1038 pub fn is_proc_macro(&self) -> bool {
1039 match self.build_target(&BuildTargetId::Library) {
1040 Some(build_target) => matches!(build_target.kind(), BuildTargetKind::ProcMacro),
1041 None => false,
1042 }
1043 }
1044
1045 /// Returns true if this package has a build script.
1046 ///
1047 /// Cargo only follows build dependencies if a build script is set.
1048 ///
1049 /// For more about build scripts, see [Build
1050 /// Scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) in the Cargo
1051 /// reference.
1052 pub fn has_build_script(&self) -> bool {
1053 self.build_target(&BuildTargetId::BuildScript).is_some()
1054 }
1055
1056 /// Returns true if this package has a named feature named `default`.
1057 ///
1058 /// For more about default features, see [The `[features]`
1059 /// section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section) in
1060 /// the Cargo reference.
1061 pub fn has_default_feature(&self) -> bool {
1062 self.inner.has_default_feature
1063 }
1064
1065 /// Returns the `FeatureId` corresponding to the default feature.
1066 pub fn default_feature_id(&self) -> FeatureId<'g> {
1067 if self.inner.has_default_feature {
1068 FeatureId::new(self.id(), FeatureLabel::Named("default"))
1069 } else {
1070 FeatureId::base(self.id())
1071 }
1072 }
1073
1074 /// Returns the list of named features available for this package. This will include a feature
1075 /// named "default" if it is defined.
1076 ///
1077 /// A named feature is listed in the `[features]` section of `Cargo.toml`. For more, see
1078 /// [the reference](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section).
1079 pub fn named_features(&self) -> impl Iterator<Item = &'g str> + 'g + use<'g> {
1080 self.named_features_full()
1081 .map(|(_, named_feature, _)| named_feature)
1082 }
1083
1084 // ---
1085 // Helper methods
1086 // --
1087
1088 #[inline]
1089 pub(super) fn package_ix(&self) -> NodeIndex<PackageIx> {
1090 self.inner.package_ix
1091 }
1092
1093 fn link_between_impl(
1094 &self,
1095 other: &PackageId,
1096 dir: Direction,
1097 ) -> Result<Option<PackageLink<'g>>, Error> {
1098 let other_ix = self.graph.package_ix(other)?;
1099 match dir {
1100 Direction::Outgoing => Ok(self.graph.link_between_ixs(self.package_ix(), other_ix)),
1101 Direction::Incoming => Ok(self.graph.link_between_ixs(other_ix, self.package_ix())),
1102 }
1103 }
1104
1105 fn direct_links_impl(
1106 &self,
1107 dir: Direction,
1108 ) -> impl Iterator<Item = PackageLink<'g>> + 'g + use<'g> {
1109 self.graph.dep_links_ixs_directed(self.package_ix(), dir)
1110 }
1111
1112 pub(super) fn get_feature_idx(&self, label: FeatureLabel<'_>) -> Option<FeatureIndexInPackage> {
1113 match label {
1114 FeatureLabel::Base => Some(FeatureIndexInPackage::Base),
1115 FeatureLabel::OptionalDependency(dep_name) => self
1116 .inner
1117 .optional_deps
1118 .get_index_of(dep_name)
1119 .map(FeatureIndexInPackage::OptionalDependency),
1120 FeatureLabel::Named(feature_name) => self
1121 .inner
1122 .named_features
1123 .get_index_of(feature_name)
1124 .map(FeatureIndexInPackage::Named),
1125 }
1126 }
1127
1128 pub(super) fn feature_idx_to_label(&self, idx: FeatureIndexInPackage) -> FeatureLabel<'g> {
1129 match idx {
1130 FeatureIndexInPackage::Base => FeatureLabel::Base,
1131 FeatureIndexInPackage::OptionalDependency(idx) => FeatureLabel::OptionalDependency(
1132 self.inner
1133 .optional_deps
1134 .get_index(idx)
1135 .expect("feature idx in optional_deps should be valid")
1136 .as_ref(),
1137 ),
1138 FeatureIndexInPackage::Named(idx) => FeatureLabel::Named(
1139 self.inner
1140 .named_features
1141 .get_index(idx)
1142 .expect("feature idx in optional_deps should be valid")
1143 .0
1144 .as_ref(),
1145 ),
1146 }
1147 }
1148
1149 #[allow(dead_code)]
1150 pub(super) fn all_feature_nodes(&self) -> impl Iterator<Item = FeatureNode> + 'g + use<'g> {
1151 let package_ix = self.package_ix();
1152 iter::once(FeatureNode::new(
1153 self.package_ix(),
1154 FeatureIndexInPackage::Base,
1155 ))
1156 .chain(
1157 (0..self.inner.named_features.len())
1158 .map(move |named_idx| FeatureNode::named_feature(package_ix, named_idx)),
1159 )
1160 .chain(
1161 (0..self.inner.optional_deps.len())
1162 .map(move |dep_idx| FeatureNode::optional_dep(package_ix, dep_idx)),
1163 )
1164 }
1165
1166 pub(super) fn named_features_full(
1167 &self,
1168 ) -> impl Iterator<Item = (FeatureIndexInPackage, &'g str, &'g [NamedFeatureDep])> + 'g + use<'g>
1169 {
1170 self.inner
1171 .named_features
1172 .iter()
1173 // IndexMap is documented to use indexes 0..n without holes, so this enumerate()
1174 // is correct.
1175 .enumerate()
1176 .map(|(idx, (feature, deps))| {
1177 (
1178 FeatureIndexInPackage::Named(idx),
1179 feature.as_ref(),
1180 deps.as_slice(),
1181 )
1182 })
1183 }
1184
1185 pub(super) fn optional_deps_full(
1186 &self,
1187 ) -> impl Iterator<Item = (FeatureIndexInPackage, &'g str)> + 'g + use<'g> {
1188 self.inner
1189 .optional_deps
1190 .iter()
1191 // IndexMap is documented to use indexes 0..n without holes, so this enumerate()
1192 // is correct.
1193 .enumerate()
1194 .map(|(idx, dep_name)| {
1195 (
1196 FeatureIndexInPackage::OptionalDependency(idx),
1197 dep_name.as_ref(),
1198 )
1199 })
1200 }
1201}
1202
1203#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1204pub(crate) enum FeatureIndexInPackage {
1205 Base,
1206 OptionalDependency(usize),
1207 Named(usize),
1208}
1209
1210/// `PackageMetadata`'s `PartialEq` implementation uses pointer equality for the `PackageGraph`.
1211impl PartialEq for PackageMetadata<'_> {
1212 fn eq(&self, other: &Self) -> bool {
1213 // Checking for the same package ix is enough as each package is guaranteed to be a 1:1 map
1214 // with ixs.
1215 std::ptr::eq(self.graph, other.graph) && self.package_ix() == other.package_ix()
1216 }
1217}
1218
1219impl Eq for PackageMetadata<'_> {}
1220
1221#[derive(Clone, Debug)]
1222pub(crate) struct PackageMetadataImpl {
1223 // Implementation note: we use Box<str> and Box<Path> to save on memory use when possible.
1224
1225 // Fields extracted from the package.
1226 pub(super) name: Box<str>,
1227 pub(super) version: Version,
1228 pub(super) authors: Vec<String>,
1229 pub(super) description: Option<Box<str>>,
1230 pub(super) license: Option<Box<str>>,
1231 pub(super) license_file: Option<Box<Utf8Path>>,
1232 pub(super) manifest_path: Box<Utf8Path>,
1233 pub(super) categories: Vec<String>,
1234 pub(super) keywords: Vec<String>,
1235 pub(super) readme: Option<Box<Utf8Path>>,
1236 pub(super) repository: Option<Box<str>>,
1237 pub(super) homepage: Option<Box<str>>,
1238 pub(super) documentation: Option<Box<str>>,
1239 pub(super) edition: Box<str>,
1240 pub(super) metadata_table: JsonValue,
1241 pub(super) links: Option<Box<str>>,
1242 pub(super) publish: PackagePublishImpl,
1243 pub(super) default_run: Option<Box<str>>,
1244 pub(super) rust_version: Option<Version>,
1245 pub(super) rust_version_req: Option<VersionReq>,
1246 pub(super) named_features: IndexMap<Box<str>, SmallVec<[NamedFeatureDep; 4]>>,
1247 pub(super) optional_deps: IndexSet<Box<str>>,
1248
1249 // Other information.
1250 pub(super) package_ix: NodeIndex<PackageIx>,
1251 pub(super) source: PackageSourceImpl,
1252 pub(super) build_targets: BTreeMap<OwnedBuildTargetId, BuildTargetImpl>,
1253 pub(super) has_default_feature: bool,
1254}
1255
1256/// The source of a package.
1257///
1258/// This enum contains information about where a package is found, and whether it is inside or
1259/// outside the workspace.
1260#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1261pub enum PackageSource<'g> {
1262 /// This package is in the workspace.
1263 ///
1264 /// The path is relative to the workspace root.
1265 Workspace(&'g Utf8Path),
1266
1267 /// This package is a path dependency that isn't in the workspace.
1268 ///
1269 /// The path is relative to the workspace root.
1270 Path(&'g Utf8Path),
1271
1272 /// This package is an external dependency.
1273 ///
1274 /// * For packages retrieved from `crates.io`, the source is the string
1275 /// `"registry+https://github.com/rust-lang/crates.io-index"`.
1276 /// * For packages retrieved from other registries, the source begins with `"registry+"`.
1277 /// * For packages retrieved from Git repositories, the source begins with `"git+"`.
1278 External(&'g str),
1279}
1280
1281assert_covariant!(PackageSource);
1282
1283impl<'g> PackageSource<'g> {
1284 /// The path to the crates.io registry.
1285 pub const CRATES_IO_REGISTRY: &'static str =
1286 "registry+https://github.com/rust-lang/crates.io-index";
1287
1288 pub(super) fn new(inner: &'g PackageSourceImpl) -> Self {
1289 match inner {
1290 PackageSourceImpl::Workspace(path) => PackageSource::Workspace(path),
1291 PackageSourceImpl::Path(path) => PackageSource::Path(path),
1292 PackageSourceImpl::CratesIo => PackageSource::External(Self::CRATES_IO_REGISTRY),
1293 PackageSourceImpl::External(source) => PackageSource::External(source),
1294 }
1295 }
1296
1297 /// Returns true if this package source represents a workspace.
1298 pub fn is_workspace(&self) -> bool {
1299 matches!(self, PackageSource::Workspace(_))
1300 }
1301
1302 /// Returns true if this package source represents a path dependency that isn't in the
1303 /// workspace.
1304 pub fn is_path(&self) -> bool {
1305 matches!(self, PackageSource::Path(_))
1306 }
1307
1308 /// Returns true if this package source represents an external dependency.
1309 pub fn is_external(&self) -> bool {
1310 matches!(self, PackageSource::External(_))
1311 }
1312
1313 /// Returns true if the source is `crates.io`.
1314 pub fn is_crates_io(&self) -> bool {
1315 matches!(self, PackageSource::External(Self::CRATES_IO_REGISTRY))
1316 }
1317
1318 /// Returns true if this package is a local dependency, i.e. either in the workspace or a local
1319 /// path.
1320 pub fn is_local(&self) -> bool {
1321 !self.is_external()
1322 }
1323
1324 /// Returns the path if this is a workspace dependency, or `None` if this is a non-workspace
1325 /// dependency.
1326 ///
1327 /// The path is relative to the workspace root.
1328 pub fn workspace_path(&self) -> Option<&'g Utf8Path> {
1329 match self {
1330 PackageSource::Workspace(path) => Some(path),
1331 _ => None,
1332 }
1333 }
1334
1335 /// Returns the local path if this is a local dependency, or `None` if it is an external
1336 /// dependency.
1337 ///
1338 /// The path is relative to the workspace root.
1339 pub fn local_path(&self) -> Option<&'g Utf8Path> {
1340 match self {
1341 PackageSource::Path(path) | PackageSource::Workspace(path) => Some(path),
1342 _ => None,
1343 }
1344 }
1345
1346 /// Returns the external source if this is an external dependency, or `None` if it is a local
1347 /// dependency.
1348 pub fn external_source(&self) -> Option<&'g str> {
1349 match self {
1350 PackageSource::External(source) => Some(source),
1351 _ => None,
1352 }
1353 }
1354
1355 /// Attempts to parse an external source.
1356 ///
1357 /// Returns `None` if the external dependency could not be recognized, or if it is a local
1358 /// dependency.
1359 ///
1360 /// For more about external sources, see the documentation for [`ExternalSource`](ExternalSource).
1361 pub fn parse_external(&self) -> Option<ExternalSource<'g>> {
1362 match self {
1363 PackageSource::External(source) => ExternalSource::new(source),
1364 _ => None,
1365 }
1366 }
1367}
1368
1369impl fmt::Display for PackageSource<'_> {
1370 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1371 match self {
1372 PackageSource::Workspace(path) => write!(f, "{path}"),
1373 PackageSource::Path(path) => write!(f, "{path}"),
1374 PackageSource::External(source) => write!(f, "{source}"),
1375 }
1376 }
1377}
1378
1379/// More information about an external source.
1380///
1381/// This provides information about whether an external dependency is a Git dependency or fetched
1382/// from a registry.
1383///
1384/// Returned by [`PackageSource::parse_external`](PackageSource::parse_external).
1385#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1386#[non_exhaustive]
1387pub enum ExternalSource<'g> {
1388 /// This is a registry source, e.g. `"registry+https://github.com/rust-lang/crates.io-index"`.
1389 ///
1390 /// The associated data is the part of the string after the initial `"registry+"`.
1391 ///
1392 /// # Examples
1393 ///
1394 /// ```
1395 /// use guppy::graph::ExternalSource;
1396 ///
1397 /// let source = "registry+https://github.com/rust-lang/crates.io-index";
1398 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1399 ///
1400 /// assert_eq!(
1401 /// parsed,
1402 /// ExternalSource::Registry("https://github.com/rust-lang/crates.io-index"),
1403 /// );
1404 /// ```
1405 Registry(&'g str),
1406
1407 /// This is a registry source that uses the [sparse registry protocol][sparse], e.g. `"sparse+https://index.crates.io"`.
1408 ///
1409 /// The associated data is the part of the string after the initial `"sparse+"`.
1410 ///
1411 /// # Examples
1412 ///
1413 /// ```
1414 /// use guppy::graph::ExternalSource;
1415 ///
1416 /// let source = "sparse+https://index.crates.io";
1417 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1418 ///
1419 /// assert_eq!(
1420 /// parsed,
1421 /// ExternalSource::Sparse("https://index.crates.io"),
1422 /// );
1423 /// ```
1424 ///
1425 /// [sparse]: https://doc.rust-lang.org/cargo/reference/registry-index.html#sparse-protocol
1426 Sparse(&'g str),
1427
1428 /// This is a Git source.
1429 ///
1430 /// An example of a Git source string is `"git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1431 /// In this case, the `Cargo.toml` would have contained:
1432 ///
1433 /// ```toml
1434 /// cargo = { git = "https://github.com/rust-lang/cargo.git", branch = "main" }
1435 /// ```
1436 ///
1437 /// and the `Cargo.lock` would have contained:
1438 ///
1439 /// ```toml
1440 /// [[package]]
1441 /// name = "cargo"
1442 /// version = "0.46.0"
1443 /// source = "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4
1444 /// dependencies = [ ... ]
1445 /// ```
1446 ///
1447 /// For more, see [Specifying dependencies from `git` repositories](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories)
1448 /// in the Cargo book.
1449 ///
1450 /// # Examples
1451 ///
1452 /// ```
1453 /// use guppy::graph::{ExternalSource, GitReq};
1454 ///
1455 /// // A branch source.
1456 /// let source = "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1457 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1458 ///
1459 /// assert_eq!(
1460 /// parsed,
1461 /// ExternalSource::Git {
1462 /// repository: "https://github.com/rust-lang/cargo.git",
1463 /// req: GitReq::Branch("main"),
1464 /// resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1465 /// }
1466 /// );
1467 ///
1468 /// // A tag source.
1469 /// let source = "git+https://github.com/rust-lang/cargo.git?tag=v0.46.0#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1470 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1471 ///
1472 /// assert_eq!(
1473 /// parsed,
1474 /// ExternalSource::Git {
1475 /// repository: "https://github.com/rust-lang/cargo.git",
1476 /// req: GitReq::Tag("v0.46.0"),
1477 /// resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1478 /// }
1479 /// );
1480 ///
1481 /// // A revision source.
1482 /// let source = "git+https://github.com/rust-lang/cargo.git?rev=0227f048fcb7c798026ede6cc20c92befc84c3a4#0227f048fcb7c798026ede6cc20c92befc84c3a4";
1483 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1484 ///
1485 /// assert_eq!(
1486 /// parsed,
1487 /// ExternalSource::Git {
1488 /// repository: "https://github.com/rust-lang/cargo.git",
1489 /// req: GitReq::Rev("0227f048fcb7c798026ede6cc20c92befc84c3a4"),
1490 /// resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1491 /// }
1492 /// );
1493 ///
1494 /// // A default source.
1495 /// let source = "git+https://github.com/gyscos/zstd-rs.git#bc874a57298bdb500cdb5aeac5f23878b6480d0b";
1496 /// let parsed = ExternalSource::new(source).expect("this source is understood by guppy");
1497 ///
1498 /// assert_eq!(
1499 /// parsed,
1500 /// ExternalSource::Git {
1501 /// repository: "https://github.com/gyscos/zstd-rs.git",
1502 /// req: GitReq::Default,
1503 /// resolved: "bc874a57298bdb500cdb5aeac5f23878b6480d0b",
1504 /// }
1505 /// );
1506 /// ```
1507 Git {
1508 /// The repository for this Git source. For the above example, this would be
1509 /// `"https://github.com/rust-lang/cargo.git"`.
1510 repository: &'g str,
1511
1512 /// The revision requested in `Cargo.toml`. This may be a tag, a branch or a specific
1513 /// revision (commit hash).
1514 ///
1515 /// For the above example, `req` would be `GitSource::Branch("main")`.
1516 req: GitReq<'g>,
1517
1518 /// The resolved revision, as specified in `Cargo.lock`.
1519 ///
1520 /// For the above example, `resolved_hash` would be `"0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1521 ///
1522 /// This is always a commit hash, and if `req` is `GitReq::Rev` then it is expected
1523 /// to be the same hash. (However, this is not verified by guppy.)
1524 resolved: &'g str,
1525 },
1526}
1527
1528impl<'g> ExternalSource<'g> {
1529 /// The string `"registry+"`.
1530 ///
1531 /// Used for matching with the `Registry` variant.
1532 pub const REGISTRY_PLUS: &'static str = "registry+";
1533
1534 /// The string `"sparse+"`.
1535 ///
1536 /// Also used for matching with the `Sparse` variant.
1537 pub const SPARSE_PLUS: &'static str = "sparse+";
1538
1539 /// The string `"git+"`.
1540 ///
1541 /// Used for matching with the `Git` variant.
1542 pub const GIT_PLUS: &'static str = "git+";
1543
1544 /// The string `"?branch="`.
1545 ///
1546 /// Used for matching with the `Git` variant.
1547 pub const BRANCH_EQ: &'static str = "?branch=";
1548
1549 /// The string `"?tag="`.
1550 ///
1551 /// Used for matching with the `Git` variant.
1552 pub const TAG_EQ: &'static str = "?tag=";
1553
1554 /// The string `"?rev="`.
1555 ///
1556 /// Used for matching with the `Git` variant.
1557 pub const REV_EQ: &'static str = "?rev=";
1558
1559 /// The URL for the `crates.io` registry.
1560 ///
1561 /// This lacks the leading `"registry+`" that's part of the [`PackageSource`].
1562 pub const CRATES_IO_URL: &'static str = "https://github.com/rust-lang/crates.io-index";
1563
1564 /// Attempts to parse the given string as an external source.
1565 ///
1566 /// Returns `None` if the string could not be recognized as an external source.
1567 pub fn new(source: &'g str) -> Option<Self> {
1568 // We *could* pull in a URL parsing library, but Cargo's sources are so limited that it
1569 // seems like a waste to.
1570 if let Some(registry) = source.strip_prefix(Self::REGISTRY_PLUS) {
1571 // A registry source.
1572 Some(ExternalSource::Registry(registry))
1573 } else if let Some(sparse) = source.strip_prefix(Self::SPARSE_PLUS) {
1574 // A sparse registry source.
1575 Some(ExternalSource::Sparse(sparse))
1576 } else if let Some(rest) = source.strip_prefix(Self::GIT_PLUS) {
1577 // A Git source.
1578 // Look for a trailing #, which indicates the resolved revision.
1579 let (rest, resolved) = rest.rsplit_once('#')?;
1580
1581 let (repository, req) = if let Some(idx) = rest.find(Self::BRANCH_EQ) {
1582 (
1583 &rest[..idx],
1584 GitReq::Branch(&rest[idx + Self::BRANCH_EQ.len()..]),
1585 )
1586 } else if let Some(idx) = rest.find(Self::TAG_EQ) {
1587 (&rest[..idx], GitReq::Tag(&rest[idx + Self::TAG_EQ.len()..]))
1588 } else if let Some(idx) = rest.find(Self::REV_EQ) {
1589 (&rest[..idx], GitReq::Rev(&rest[idx + Self::TAG_EQ.len()..]))
1590 } else {
1591 (rest, GitReq::Default)
1592 };
1593
1594 Some(ExternalSource::Git {
1595 repository,
1596 req,
1597 resolved,
1598 })
1599 } else {
1600 None
1601 }
1602 }
1603}
1604
1605/// The `Display` implementation for `ExternalSource` returns the string it was constructed from.
1606///
1607/// # Examples
1608///
1609/// ```
1610/// use guppy::graph::{ExternalSource, GitReq};
1611///
1612/// let source = ExternalSource::Git {
1613/// repository: "https://github.com/rust-lang/cargo.git",
1614/// req: GitReq::Branch("main"),
1615/// resolved: "0227f048fcb7c798026ede6cc20c92befc84c3a4",
1616/// };
1617///
1618/// assert_eq!(
1619/// format!("{}", source),
1620/// "git+https://github.com/rust-lang/cargo.git?branch=main#0227f048fcb7c798026ede6cc20c92befc84c3a4",
1621/// );
1622/// ```
1623impl fmt::Display for ExternalSource<'_> {
1624 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1625 match self {
1626 ExternalSource::Registry(url) => write!(f, "{}{}", Self::REGISTRY_PLUS, url),
1627 ExternalSource::Sparse(url) => write!(f, "{}{}", Self::SPARSE_PLUS, url),
1628 ExternalSource::Git {
1629 repository,
1630 req,
1631 resolved,
1632 } => {
1633 write!(f, "{}{}", Self::GIT_PLUS, repository)?;
1634 match req {
1635 GitReq::Branch(branch) => write!(f, "{}{}", Self::BRANCH_EQ, branch)?,
1636 GitReq::Tag(tag) => write!(f, "{}{}", Self::TAG_EQ, tag)?,
1637 GitReq::Rev(rev) => write!(f, "{}{}", Self::REV_EQ, rev)?,
1638 GitReq::Default => {}
1639 };
1640 write!(f, "#{resolved}")
1641 }
1642 }
1643 }
1644}
1645
1646/// A `Cargo.toml` specification for a Git branch, tag, or revision.
1647///
1648/// For more, including examples, see the documentation for [`ExternalSource::Git`](ExternalSource::Git).
1649#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1650#[non_exhaustive]
1651pub enum GitReq<'g> {
1652 /// A branch, e.g. `"main"`.
1653 ///
1654 /// This is specified in `Cargo.toml` as:
1655 ///
1656 /// ```toml
1657 /// [dependencies]
1658 /// cargo = { git = "...", branch = "main" }
1659 /// ```
1660 Branch(&'g str),
1661
1662 /// A tag, e.g. `"guppy-0.5.0"`.
1663 ///
1664 /// This is specified in `Cargo.toml` as:
1665 ///
1666 /// ```toml
1667 /// [dependencies]
1668 /// guppy = { git = "...", tag = "guppy-0.5.0" }
1669 /// ```
1670 Tag(&'g str),
1671
1672 /// A revision (commit hash), e.g. `"0227f048fcb7c798026ede6cc20c92befc84c3a4"`.
1673 ///
1674 /// This is specified in `Cargo.toml` as:
1675 ///
1676 /// ```toml
1677 /// [dependencies]
1678 /// cargo = { git = "...", rev = "0227f048fcb7c798026ede6cc20c92befc84c3a4" }
1679 /// ```
1680 Rev(&'g str),
1681
1682 /// Not specified in `Cargo.toml`. Cargo treats this as the main branch by default.
1683 ///
1684 /// ```toml
1685 /// [dependencies]
1686 /// cargo = { git = "..." }
1687 /// ```
1688 Default,
1689}
1690
1691/// Internal representation of the source of a package.
1692#[derive(Clone, Debug, PartialEq, Eq)]
1693pub(super) enum PackageSourceImpl {
1694 Workspace(Box<Utf8Path>),
1695 Path(Box<Utf8Path>),
1696 // Special, common case.
1697 CratesIo,
1698 External(Box<str>),
1699}
1700
1701/// Locations that a package can be published to.
1702///
1703/// Returned by [`PackageMetadata::publish`].
1704#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1705#[non_exhaustive]
1706pub enum PackagePublish<'g> {
1707 /// Publication of this package is unrestricted.
1708 Unrestricted,
1709
1710 /// This package can only be published to the listed [package registry].
1711 ///
1712 /// If the list is empty, this package cannot be published to any registries.
1713 ///
1714 /// [package registry]: https://doc.rust-lang.org/cargo/reference/registries.html
1715 Registries(&'g [String]),
1716}
1717
1718// TODO: implement PartialOrd/Ord for these as well using lattice rules
1719
1720assert_covariant!(PackagePublish);
1721
1722impl<'g> PackagePublish<'g> {
1723 pub(super) fn new(inner: &'g PackagePublishImpl) -> Self {
1724 match inner {
1725 PackagePublishImpl::Unrestricted => PackagePublish::Unrestricted,
1726 PackagePublishImpl::Registries(registries) => PackagePublish::Registries(registries),
1727 }
1728 }
1729
1730 /// The string `"crates-io"`, indicating that a package can be published to
1731 /// [crates.io](https://crates.io/).
1732 pub const CRATES_IO: &'static str = "crates-io";
1733
1734 /// Returns true if this package can be published to any package registry.
1735 ///
1736 /// # Examples
1737 ///
1738 /// ```
1739 /// use guppy::graph::PackagePublish;
1740 ///
1741 /// assert!(PackagePublish::Unrestricted.is_unrestricted());
1742 /// assert!(!PackagePublish::Registries(&[PackagePublish::CRATES_IO.to_owned()]).is_unrestricted());
1743 /// assert!(!PackagePublish::Registries(&[]).is_unrestricted());
1744 /// ```
1745 pub fn is_unrestricted(&self) -> bool {
1746 matches!(self, PackagePublish::Unrestricted)
1747 }
1748
1749 /// Returns true if a package can be published to the given package registry.
1750 ///
1751 /// # Examples
1752 ///
1753 /// ```
1754 /// use guppy::graph::PackagePublish;
1755 ///
1756 /// // Unrestricted means this package can be published to any registry.
1757 /// assert!(PackagePublish::Unrestricted.can_publish_to(PackagePublish::CRATES_IO));
1758 /// assert!(PackagePublish::Unrestricted.can_publish_to("my-registry"));
1759 ///
1760 /// // Publish to specific registries but not others.
1761 /// let crates_io = &[PackagePublish::CRATES_IO.to_owned()];
1762 /// let crates_io_publish = PackagePublish::Registries(crates_io);
1763 /// assert!(crates_io_publish.can_publish_to(PackagePublish::CRATES_IO));
1764 /// assert!(!crates_io_publish.can_publish_to("my-registry"));
1765 ///
1766 /// // Cannot publish to any registries.
1767 /// assert!(!PackagePublish::Registries(&[]).can_publish_to(PackagePublish::CRATES_IO));
1768 /// ```
1769 pub fn can_publish_to(&self, registry: impl AsRef<str>) -> bool {
1770 let registry = registry.as_ref();
1771 match self {
1772 PackagePublish::Unrestricted => true,
1773 PackagePublish::Registries(registries) => registries.iter().any(|r| r == registry),
1774 }
1775 }
1776
1777 /// Returns true if a package can be published to crates.io.
1778 pub fn can_publish_to_crates_io(&self) -> bool {
1779 self.can_publish_to(Self::CRATES_IO)
1780 }
1781
1782 /// Returns true if a package cannot be published to any registries.
1783 ///
1784 /// # Examples
1785 ///
1786 /// ```
1787 /// use guppy::graph::PackagePublish;
1788 ///
1789 /// assert!(!PackagePublish::Unrestricted.is_never());
1790 /// assert!(!PackagePublish::Registries(&[PackagePublish::CRATES_IO.to_owned()]).is_never());
1791 /// assert!(PackagePublish::Registries(&[]).is_never());
1792 /// ```
1793 pub fn is_never(&self) -> bool {
1794 match self {
1795 PackagePublish::Unrestricted => false,
1796 PackagePublish::Registries(registries) => registries.is_empty(),
1797 }
1798 }
1799}
1800
1801/// Internal representation of PackagePublish.
1802#[derive(Clone, Debug)]
1803pub(super) enum PackagePublishImpl {
1804 Unrestricted,
1805 Registries(Box<[String]>),
1806}
1807
1808/// Represents a dependency from one package to another.
1809///
1810/// This struct contains information about:
1811/// * whether this dependency was renamed in the context of this crate.
1812/// * if this is a normal, dev and/or build dependency.
1813/// * platform-specific information about required, optional and status
1814#[derive(Copy, Clone, Debug)]
1815pub struct PackageLink<'g> {
1816 graph: &'g PackageGraph,
1817 from: &'g PackageMetadataImpl,
1818 to: &'g PackageMetadataImpl,
1819 edge_ix: EdgeIndex<PackageIx>,
1820 inner: &'g PackageLinkImpl,
1821}
1822
1823assert_covariant!(PackageLink);
1824
1825impl<'g> PackageLink<'g> {
1826 pub(super) fn new(
1827 graph: &'g PackageGraph,
1828 source_ix: NodeIndex<PackageIx>,
1829 target_ix: NodeIndex<PackageIx>,
1830 edge_ix: EdgeIndex<PackageIx>,
1831 inner: Option<&'g PackageLinkImpl>,
1832 ) -> Self {
1833 let from = graph
1834 .data
1835 .metadata_impl(&graph.dep_graph[source_ix])
1836 .expect("'from' should have associated metadata");
1837 let to = graph
1838 .data
1839 .metadata_impl(&graph.dep_graph[target_ix])
1840 .expect("'to' should have associated metadata");
1841 Self {
1842 graph,
1843 from,
1844 to,
1845 edge_ix,
1846 inner: inner.unwrap_or_else(|| &graph.dep_graph[edge_ix]),
1847 }
1848 }
1849
1850 /// Returns the package which depends on the `to` package.
1851 pub fn from(&self) -> PackageMetadata<'g> {
1852 PackageMetadata::new(self.graph, self.from)
1853 }
1854
1855 /// Returns the package which is depended on by the `from` package.
1856 pub fn to(&self) -> PackageMetadata<'g> {
1857 PackageMetadata::new(self.graph, self.to)
1858 }
1859
1860 /// Returns the endpoints as a pair of packages `(from, to)`.
1861 pub fn endpoints(&self) -> (PackageMetadata<'g>, PackageMetadata<'g>) {
1862 (self.from(), self.to())
1863 }
1864
1865 /// Returns the name for this dependency edge. This can be affected by a crate rename.
1866 pub fn dep_name(&self) -> &'g str {
1867 &self.inner.dep_name
1868 }
1869
1870 /// Returns the resolved name for this dependency edge. This may involve renaming the crate and
1871 /// replacing - with _.
1872 pub fn resolved_name(&self) -> &'g str {
1873 &self.inner.resolved_name
1874 }
1875
1876 /// Returns the semver requirements specified for this dependency.
1877 ///
1878 /// To get the resolved version, see the `to` field of the `PackageLink` this was part of.
1879 ///
1880 /// ## Notes
1881 ///
1882 /// A dependency can be requested multiple times, possibly with different version requirements,
1883 /// even if they all end up resolving to the same version. `version_req` will return any of
1884 /// those requirements.
1885 ///
1886 /// See [Specifying Dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies)
1887 /// in the Cargo reference for more details.
1888 pub fn version_req(&self) -> &'g VersionReq {
1889 &self.inner.version_req
1890 }
1891
1892 /// Returns the registry URL for this dependency, if specified.
1893 ///
1894 /// Returns `None` for dependencies from crates.io (the default registry) or
1895 /// for dependencies without an explicit registry.
1896 pub fn registry(&self) -> Option<&'g str> {
1897 self.inner.registry.as_deref()
1898 }
1899
1900 /// Returns the file system path for this dependency, if it is a path
1901 /// dependency.
1902 ///
1903 /// Returns `None` for dependencies from registries or other sources.
1904 pub fn path(&self) -> Option<&'g Utf8Path> {
1905 self.inner.path.as_deref()
1906 }
1907
1908 /// Returns details about this dependency from the `[dependencies]` section.
1909 pub fn normal(&self) -> DependencyReq<'g> {
1910 DependencyReq {
1911 inner: &self.inner.normal,
1912 }
1913 }
1914
1915 /// Returns details about this dependency from the `[build-dependencies]` section.
1916 pub fn build(&self) -> DependencyReq<'g> {
1917 DependencyReq {
1918 inner: &self.inner.build,
1919 }
1920 }
1921
1922 /// Returns details about this dependency from the `[dev-dependencies]` section.
1923 pub fn dev(&self) -> DependencyReq<'g> {
1924 DependencyReq {
1925 inner: &self.inner.dev,
1926 }
1927 }
1928
1929 /// Returns details about this dependency from the section specified by the given dependency
1930 /// kind.
1931 pub fn req_for_kind(&self, kind: DependencyKind) -> DependencyReq<'g> {
1932 match kind {
1933 DependencyKind::Normal => self.normal(),
1934 DependencyKind::Development => self.dev(),
1935 DependencyKind::Build => self.build(),
1936 }
1937 }
1938
1939 /// Return true if this edge is dev-only, i.e. code from this edge will not be included in
1940 /// normal builds.
1941 pub fn dev_only(&self) -> bool {
1942 self.inner.dev_only()
1943 }
1944
1945 // ---
1946 // Helper methods
1947 // ---
1948
1949 /// Returns the edge index.
1950 #[allow(dead_code)]
1951 pub(super) fn edge_ix(&self) -> EdgeIndex<PackageIx> {
1952 self.edge_ix
1953 }
1954
1955 /// Returns (source, target, edge) as a triple of pointers. Useful for testing.
1956 #[doc(hidden)]
1957 pub fn as_inner_ptrs(&self) -> PackageLinkPtrs {
1958 PackageLinkPtrs {
1959 from: self.from,
1960 to: self.to,
1961 inner: self.inner,
1962 }
1963 }
1964}
1965
1966/// An opaque identifier for a PackageLink's pointers. Used for tests.
1967#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
1968#[doc(hidden)]
1969pub struct PackageLinkPtrs {
1970 from: *const PackageMetadataImpl,
1971 to: *const PackageMetadataImpl,
1972 inner: *const PackageLinkImpl,
1973}
1974
1975#[derive(Clone, Debug)]
1976pub(crate) struct PackageLinkImpl {
1977 pub(super) dep_name: String,
1978 pub(super) resolved_name: String,
1979 pub(super) version_req: VersionReq,
1980 pub(super) registry: Option<String>,
1981 pub(super) path: Option<Utf8PathBuf>,
1982 pub(super) normal: DependencyReqImpl,
1983 pub(super) build: DependencyReqImpl,
1984 pub(super) dev: DependencyReqImpl,
1985}
1986
1987impl PackageLinkImpl {
1988 #[inline]
1989 fn dev_only(&self) -> bool {
1990 self.normal.enabled().is_never() && self.build.enabled().is_never()
1991 }
1992}
1993
1994/// Information about a specific kind of dependency (normal, build or dev) from a package to another
1995/// package.
1996///
1997/// Usually found within the context of a [`PackageLink`](struct.PackageLink.html).
1998#[derive(Clone, Debug)]
1999pub struct DependencyReq<'g> {
2000 pub(super) inner: &'g DependencyReqImpl,
2001}
2002
2003impl<'g> DependencyReq<'g> {
2004 /// Returns true if there is at least one `Cargo.toml` entry corresponding to this requirement.
2005 ///
2006 /// For example, if this dependency is specified in the `[dev-dependencies]` section,
2007 /// `edge.dev().is_present()` will return true.
2008 pub fn is_present(&self) -> bool {
2009 !self.inner.enabled().is_never()
2010 }
2011
2012 /// Returns the enabled status of this dependency.
2013 ///
2014 /// `status` is the union of `default_features` and `no_default_features`.
2015 ///
2016 /// See the documentation for `EnabledStatus` for more.
2017 pub fn status(&self) -> EnabledStatus<'g> {
2018 self.inner.enabled()
2019 }
2020
2021 /// Returns the enabled status of this dependency when `default-features = true`.
2022 ///
2023 /// See the documentation for `EnabledStatus` for more.
2024 pub fn default_features(&self) -> EnabledStatus<'g> {
2025 self.inner.default_features()
2026 }
2027
2028 /// Returns the enabled status of this dependency when `default-features = false`.
2029 ///
2030 /// This is generally less useful than `status` or `default_features`, but is provided for
2031 /// completeness.
2032 ///
2033 /// See the documentation for `EnabledStatus` for more.
2034 pub fn no_default_features(&self) -> EnabledStatus<'g> {
2035 self.inner.no_default_features()
2036 }
2037
2038 /// Returns a list of all features possibly enabled by this dependency. This includes features
2039 /// that are only turned on if the dependency is optional, or features enabled by inactive
2040 /// platforms.
2041 pub fn features(&self) -> impl Iterator<Item = &'g str> + use<'g> {
2042 self.inner.all_features()
2043 }
2044
2045 /// Returns the enabled status of this feature.
2046 ///
2047 /// Note that as of Rust 1.42, the default feature resolver behaves in potentially surprising
2048 /// ways. See the [Cargo
2049 /// reference](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#features) for
2050 /// more.
2051 ///
2052 /// See the documentation for `EnabledStatus` for more.
2053 pub fn feature_status(&self, feature: &str) -> EnabledStatus<'g> {
2054 self.inner.feature_status(feature)
2055 }
2056}
2057
2058/// Whether a dependency or feature is required, optional, or disabled.
2059///
2060/// Returned by the methods on `DependencyMetadata`.
2061///
2062/// ## Examples
2063///
2064/// ```toml
2065/// [dependencies]
2066/// once_cell = "1"
2067/// ```
2068///
2069/// The dependency and default features are *required* on all platforms.
2070///
2071/// ```toml
2072/// [dependencies]
2073/// once_cell = { version = "1", optional = true }
2074/// ```
2075///
2076/// The dependency and default features are *optional* on all platforms.
2077///
2078/// ```toml
2079/// [target.'cfg(windows)'.dependencies]
2080/// once_cell = { version = "1", optional = true }
2081/// ```
2082///
2083/// The result is platform-dependent. On Windows, the dependency and default features are both
2084/// *optional*. On non-Windows platforms, the dependency and default features are *disabled*.
2085///
2086/// ```toml
2087/// [dependencies]
2088/// once_cell = { version = "1", optional = true }
2089///
2090/// [target.'cfg(windows)'.dependencies]
2091/// once_cell = { version = "1", optional = false, default-features = false }
2092/// ```
2093///
2094/// The result is platform-dependent. On Windows, the dependency is *mandatory* and default features
2095/// are *optional* (i.e. enabled if the `once_cell` feature is turned on).
2096///
2097/// On Unix platforms, the dependency and default features are both *optional*.
2098#[derive(Copy, Clone, Debug)]
2099pub struct EnabledStatus<'g> {
2100 required: PlatformStatus<'g>,
2101 optional: PlatformStatus<'g>,
2102}
2103
2104assert_covariant!(EnabledStatus);
2105
2106impl<'g> EnabledStatus<'g> {
2107 pub(super) fn new(required: &'g PlatformStatusImpl, optional: &'g PlatformStatusImpl) -> Self {
2108 Self {
2109 required: PlatformStatus::new(required),
2110 optional: PlatformStatus::new(optional),
2111 }
2112 }
2113
2114 /// Returns true if this dependency is never enabled on any platform.
2115 pub fn is_never(&self) -> bool {
2116 self.required.is_never() && self.optional.is_never()
2117 }
2118
2119 /// Evaluates whether this dependency is required on the given platform spec.
2120 ///
2121 /// Returns `Unknown` if the result was unknown, which may happen if evaluating against an
2122 /// individual platform and its target features are unknown.
2123 pub fn required_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
2124 self.required.enabled_on(platform_spec)
2125 }
2126
2127 /// Evaluates whether this dependency is enabled (required or optional) on the given platform
2128 /// spec.
2129 ///
2130 /// Returns `Unknown` if the result was unknown, which may happen if evaluating against an
2131 /// individual platform and its target features are unknown.
2132 pub fn enabled_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
2133 let required = self.required.enabled_on(platform_spec);
2134 let optional = self.optional.enabled_on(platform_spec);
2135
2136 required | optional
2137 }
2138
2139 /// Returns the `PlatformStatus` corresponding to whether this dependency is required.
2140 pub fn required_status(&self) -> PlatformStatus<'g> {
2141 self.required
2142 }
2143
2144 /// Returns the `PlatformStatus` corresponding to whether this dependency is optional.
2145 pub fn optional_status(&self) -> PlatformStatus<'g> {
2146 self.optional
2147 }
2148}
2149
2150#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
2151pub(super) enum NamedFeatureDep {
2152 NamedFeature(Box<str>),
2153 OptionalDependency(Box<str>),
2154 DependencyNamedFeature {
2155 dep_name: Box<str>,
2156 feature: Box<str>,
2157 weak: bool,
2158 },
2159}
2160
2161impl NamedFeatureDep {
2162 #[inline]
2163 pub(super) fn named_feature(feature_name: impl Into<String>) -> Self {
2164 Self::NamedFeature(feature_name.into().into_boxed_str())
2165 }
2166
2167 #[inline]
2168 pub(super) fn optional_dependency(dep_name: impl Into<String>) -> Self {
2169 Self::OptionalDependency(dep_name.into().into_boxed_str())
2170 }
2171
2172 #[inline]
2173 pub(super) fn dep_named_feature(
2174 dep_name: impl Into<String>,
2175 feature: impl Into<String>,
2176 weak: bool,
2177 ) -> Self {
2178 Self::DependencyNamedFeature {
2179 dep_name: dep_name.into().into_boxed_str(),
2180 feature: feature.into().into_boxed_str(),
2181 weak,
2182 }
2183 }
2184}
2185
2186impl fmt::Display for NamedFeatureDep {
2187 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2188 match self {
2189 Self::NamedFeature(feature) => write!(f, "{feature}"),
2190 Self::OptionalDependency(dep_name) => write!(f, "dep:{dep_name}"),
2191 Self::DependencyNamedFeature {
2192 dep_name,
2193 feature,
2194 weak,
2195 } => {
2196 write!(
2197 f,
2198 "{}{}/{}",
2199 dep_name,
2200 if *weak { "?" } else { "" },
2201 feature
2202 )
2203 }
2204 }
2205 }
2206}
2207
2208/// Information about dependency requirements.
2209#[derive(Clone, Debug, Default)]
2210pub(super) struct DependencyReqImpl {
2211 pub(super) required: DepRequiredOrOptional,
2212 pub(super) optional: DepRequiredOrOptional,
2213}
2214
2215impl DependencyReqImpl {
2216 fn all_features(&self) -> impl Iterator<Item = &str> {
2217 self.required
2218 .all_features()
2219 .chain(self.optional.all_features())
2220 }
2221
2222 pub(super) fn enabled(&self) -> EnabledStatus<'_> {
2223 self.make_status(|req_impl| &req_impl.build_if)
2224 }
2225
2226 pub(super) fn default_features(&self) -> EnabledStatus<'_> {
2227 self.make_status(|req_impl| &req_impl.default_features_if)
2228 }
2229
2230 pub(super) fn no_default_features(&self) -> EnabledStatus<'_> {
2231 self.make_status(|req_impl| &req_impl.no_default_features_if)
2232 }
2233
2234 pub(super) fn feature_status(&self, feature: &str) -> EnabledStatus<'_> {
2235 // This PlatformStatusImpl in static memory is so that the lifetimes work out.
2236 static DEFAULT_STATUS: PlatformStatusImpl = PlatformStatusImpl::Specs(Vec::new());
2237
2238 self.make_status(|req_impl| {
2239 req_impl
2240 .feature_targets
2241 .get(feature)
2242 .unwrap_or(&DEFAULT_STATUS)
2243 })
2244 }
2245
2246 fn make_status(
2247 &self,
2248 pred_fn: impl Fn(&DepRequiredOrOptional) -> &PlatformStatusImpl,
2249 ) -> EnabledStatus<'_> {
2250 EnabledStatus::new(pred_fn(&self.required), pred_fn(&self.optional))
2251 }
2252}
2253
2254/// Information about dependency requirements, scoped to either the dependency being required or
2255/// optional.
2256#[derive(Clone, Debug, Default)]
2257pub(super) struct DepRequiredOrOptional {
2258 pub(super) build_if: PlatformStatusImpl,
2259 pub(super) default_features_if: PlatformStatusImpl,
2260 pub(super) no_default_features_if: PlatformStatusImpl,
2261 pub(super) feature_targets: BTreeMap<String, PlatformStatusImpl>,
2262}
2263
2264impl DepRequiredOrOptional {
2265 pub(super) fn all_features(&self) -> impl Iterator<Item = &str> {
2266 self.feature_targets.keys().map(|s| s.as_str())
2267 }
2268}