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