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