Skip to main content

pedant_core/resolution/rust/snapshot/
primary.rs

1//! Root-only snapshots for every primary target one package publishes.
2//!
3//! Library, binary, and build-script closures keep separate target views while
4//! sharing one source store. A normalized source reached by several targets is
5//! therefore read, hashed, and parsed once without erasing which targets
6//! reached it.
7
8use std::sync::Arc;
9
10use crate::resolution::rust::identity::{PackageId, TargetId};
11use crate::resolution::rust::project::RustProject;
12use crate::resolution::rust::target::{CargoTargetKind, RustTarget};
13
14use super::authority;
15use super::closure::{self, ClosureEntry, UnitClosure};
16use super::error::RustSnapshotError;
17use super::source::{self, RustSource};
18use super::store::{SourceStore, refuse};
19
20/// One primary target's closure inside a package snapshot.
21#[derive(Debug)]
22pub struct RustPrimaryTargetSnapshot {
23    target: TargetId,
24    kind: CargoTargetKind,
25    crate_root: Arc<str>,
26    sources: Box<[Arc<str>]>,
27}
28
29impl RustPrimaryTargetSnapshot {
30    /// The target whose closure this view describes.
31    pub fn target(&self) -> TargetId {
32        self.target
33    }
34
35    /// Whether this is a library, binary, or build-script target.
36    pub fn kind(&self) -> CargoTargetKind {
37        self.kind
38    }
39
40    /// The repository-relative entry point this closure started at.
41    pub fn crate_root(&self) -> &str {
42        &self.crate_root
43    }
44
45    /// Every repository-relative source path this target reaches.
46    pub fn sources(&self) -> impl ExactSizeIterator<Item = &str> {
47        self.sources.iter().map(AsRef::as_ref)
48    }
49}
50
51/// Every primary target of one package and their shared source store.
52#[derive(Debug)]
53pub struct RustPackageSnapshot {
54    package: PackageId,
55    targets: Box<[RustPrimaryTargetSnapshot]>,
56    sources: Box<[RustSource]>,
57}
58
59impl RustPackageSnapshot {
60    /// The package whose primary targets were snapshotted.
61    pub fn package(&self) -> PackageId {
62        self.package
63    }
64
65    /// The package's library, binary, and build-script target views.
66    pub fn targets(&self) -> &[RustPrimaryTargetSnapshot] {
67        &self.targets
68    }
69
70    /// Every distinct source reached by any primary target, sorted by path.
71    pub fn sources(&self) -> &[RustSource] {
72        &self.sources
73    }
74
75    /// The one stored source at a repository-relative path.
76    pub fn source(&self, path: &str) -> Option<&RustSource> {
77        source::find(&self.sources, path)
78    }
79}
80
81/// A package snapshot failed while validating the package or walking one
82/// primary target.
83#[derive(Debug, thiserror::Error)]
84#[error("{source}")]
85pub struct RustPackageSnapshotError {
86    target: Option<TargetId>,
87    #[source]
88    source: RustSnapshotError,
89}
90
91impl RustPackageSnapshotError {
92    /// The primary target whose closure failed, when source traversal began.
93    pub fn target(&self) -> Option<TargetId> {
94        self.target
95    }
96
97    /// Consume the package context and return the underlying typed failure.
98    pub fn into_source(self) -> RustSnapshotError {
99        self.source
100    }
101
102    fn package(source: RustSnapshotError) -> Self {
103        Self {
104            target: None,
105            source,
106        }
107    }
108
109    fn for_target(target: TargetId, source: RustSnapshotError) -> Self {
110        Self {
111            target: Some(target),
112            source,
113        }
114    }
115}
116
117/// Validate one package, then walk all of its primary targets into one store.
118pub(in crate::resolution::rust) fn build(
119    project: &RustProject,
120    id: PackageId,
121) -> Result<RustPackageSnapshot, RustPackageSnapshotError> {
122    let package =
123        authority::validated_package(project, id).map_err(RustPackageSnapshotError::package)?;
124    let primary_targets = project
125        .package_targets(package.id())
126        .filter(|target| is_primary(target.kind()));
127    let mut store = SourceStore::new(project.root(), project.limits());
128    let mut targets = Vec::new();
129    for target in primary_targets {
130        targets.push(build_target(&mut store, target)?);
131    }
132    Ok(RustPackageSnapshot {
133        package: id,
134        targets: targets.into_boxed_slice(),
135        sources: store.finish(),
136    })
137}
138
139fn build_target(
140    store: &mut SourceStore,
141    target: &RustTarget,
142) -> Result<RustPrimaryTargetSnapshot, RustPackageSnapshotError> {
143    let entry = ClosureEntry {
144        target_name: target.name(),
145        entry_path: target.entry_path(),
146        edition: target.edition(),
147    };
148    let mut failures = Vec::new();
149    let closure = closure::walk_unit(store, &entry, &mut failures);
150    match (closure, failures.is_empty()) {
151        (Some(closure), true) => Ok(target_snapshot(target, closure)),
152        (None, _) | (Some(_), false) => Err(RustPackageSnapshotError::for_target(
153            target.id(),
154            refuse(store, failures),
155        )),
156    }
157}
158
159fn target_snapshot(target: &RustTarget, closure: UnitClosure) -> RustPrimaryTargetSnapshot {
160    RustPrimaryTargetSnapshot {
161        target: target.id(),
162        kind: target.kind(),
163        crate_root: Arc::clone(&target.entry_path),
164        sources: closure.sources,
165    }
166}
167
168fn is_primary(kind: CargoTargetKind) -> bool {
169    matches!(
170        kind,
171        CargoTargetKind::Library | CargoTargetKind::Binary | CargoTargetKind::BuildScript
172    )
173}