1use crate::{
4 manifest::{self, ManifestFile},
5 plan::{PinnedId, PinnedManifests},
6};
7use serde::{Deserialize, Serialize};
8use std::{
9 collections::hash_map,
10 fmt,
11 hash::{Hash, Hasher},
12 path::{Path, PathBuf},
13};
14use thiserror::Error;
15
16mod member;
17mod path;
18
19trait Pin {
21 type Pinned: Fetch + Hash;
22 type Error: fmt::Debug + fmt::Display;
23 fn pin(&self, ctx: PinCtx) -> Result<(Self::Pinned, PathBuf), Self::Error>;
24}
25
26trait Fetch {
28 type Error: fmt::Debug + fmt::Display;
29 fn fetch(&self, ctx: PinCtx, local: &Path) -> Result<ManifestFile, Self::Error>;
30}
31
32trait DepPath {
34 type Error: fmt::Debug + fmt::Display;
35 fn dep_path(&self, name: &str) -> Result<DependencyPath, Self::Error>;
36}
37
38#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
43pub enum Source {
44 Member(member::Source),
46 Path(path::Source),
48}
49
50#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
52pub enum Pinned {
53 Member(member::Pinned),
54 Path(path::Pinned),
55}
56
57#[derive(Clone)]
59pub(crate) struct PinCtx<'a> {
60 pub(crate) _fetch_id: FetchId,
63 pub(crate) path_root: PinnedId,
65 pub(crate) pkg_name: &'a str,
67}
68
69pub(crate) enum DependencyPath {
70 Member,
72 #[allow(dead_code)]
75 ManifestPath(PathBuf),
76 Root(PinnedId),
78}
79
80#[derive(Debug, Error)]
82pub enum SourceError {
83 #[error("failed to canonicalize path {0:?}: {1}")]
84 FailedToCanonicalizePath(std::path::PathBuf, std::io::Error),
85}
86
87#[derive(Debug, Error)]
89pub enum PinAndFetchError {
90 #[error("{0}")]
92 Path(#[from] PinAndFetchErrorKind<path::Source>),
93 #[error("{0}")]
95 Member(#[from] PinAndFetchErrorKind<member::Source>),
96}
97
98#[derive(Debug, Error)]
100#[allow(private_bounds, private_interfaces)]
101pub enum PinAndFetchErrorKind<T: Pin> {
102 #[error("failed to pin dependency source: {0}")]
103 Pin(T::Error),
104 #[error("failed to fetch dependency source: {0}")]
105 Fetch(<T::Pinned as Fetch>::Error),
106}
107
108#[derive(Debug, Error)]
110#[error("failed to resolve the path to the dependency's local source")]
111pub enum DepPathError {}
112
113pub type FetchId = u64;
114
115impl Source {
116 fn from_relative_path<'a>(
121 manifest_dir: &Path,
122 relative_path: &Path,
123 member_manifests: impl IntoIterator<Item = &'a ManifestFile>,
124 ) -> Result<Self, SourceError> {
125 let path = manifest_dir.join(relative_path);
126 let canonical_path = path
127 .canonicalize()
128 .map_err(|e| SourceError::FailedToCanonicalizePath(path, e))?;
129 let is_member = member_manifests
130 .into_iter()
131 .any(|pkg_manifest| pkg_manifest.dir() == canonical_path);
132 if is_member {
133 Ok(Source::Member(member::Source(canonical_path)))
134 } else {
135 Ok(Source::Path(canonical_path))
136 }
137 }
138
139 pub fn from_manifest_dep<'a>(
141 manifest_dir: &Path,
142 dep: &manifest::Dependency,
143 member_manifests: impl IntoIterator<Item = &'a ManifestFile>,
144 ) -> Result<Self, SourceError> {
145 match &dep.source {
146 manifest::dependency::Source::Path(path) => {
147 Self::from_relative_path(manifest_dir, &path.path, member_manifests)
148 }
149 }
150 }
151
152 pub(crate) fn pin_and_fetch(
158 &self,
159 ctx: PinCtx,
160 manifests: &mut PinnedManifests,
161 ) -> Result<Pinned, PinAndFetchError> {
162 match self {
163 Source::Member(source) => Ok(Pinned::Member(pin_and_fetch(source, ctx, manifests)?)),
164 Source::Path(source) => Ok(Pinned::Path(pin_and_fetch(source, ctx, manifests)?)),
165 }
166 }
167}
168
169impl Pinned {
170 pub const MEMBER: Self = Self::Member(member::Pinned);
172
173 pub fn unpinned(&self, path: &Path) -> Source {
175 match self {
176 Self::Member(_) => Source::Member(member::Source(path.to_owned())),
177 Self::Path(_) => Source::Path(path.to_owned()),
178 }
179 }
180
181 pub(crate) fn dep_path(&self, name: &str) -> Result<DependencyPath, DepPathError> {
183 match self {
184 Self::Member(pinned) => Ok(pinned.dep_path(name).expect("infallible")),
185 Self::Path(pinned) => Ok(pinned.dep_path(name).expect("infallible")),
186 }
187 }
188}
189
190impl fmt::Display for Pinned {
191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192 match self {
193 Self::Member(p) => p.fmt(f),
194 Self::Path(p) => p.fmt(f),
195 }
196 }
197}
198
199fn pin_and_fetch<T>(
200 source: &T,
201 ctx: PinCtx,
202 manifests: &mut PinnedManifests,
203) -> Result<T::Pinned, PinAndFetchErrorKind<T>>
204where
205 T: Pin,
206 T::Pinned: Clone,
207 Pinned: From<T::Pinned>,
208{
209 let (pinned, fetch_path) = source.pin(ctx.clone()).map_err(PinAndFetchErrorKind::Pin)?;
210 let id = PinnedId::new(ctx.pkg_name, &Pinned::from(pinned.clone()));
211 if let hash_map::Entry::Vacant(entry) = manifests.entry(id) {
212 let res = pinned.fetch(ctx, &fetch_path);
213 let manifest = res.map_err(PinAndFetchErrorKind::Fetch)?;
214 entry.insert(manifest);
215 }
216 Ok(pinned)
217}
218
219pub fn fetch_graph_id(path: &Path, timestamp: std::time::Instant) -> FetchId {
221 let mut hasher = hash_map::DefaultHasher::new();
222 path.hash(&mut hasher);
223 timestamp.hash(&mut hasher);
224 hasher.finish()
225}