1pub mod git;
11pub(crate) mod ipfs;
12mod member;
13pub mod path;
14mod reg;
15
16use self::git::Url;
17use crate::manifest::GenericManifestFile;
18use crate::{
19 manifest::{self, MemberManifestFiles, PackageManifestFile},
20 pkg::{ManifestMap, PinnedId},
21};
22use anyhow::{anyhow, bail, Result};
23use serde::{Deserialize, Serialize};
24use std::{
25 collections::hash_map,
26 fmt,
27 hash::{Hash, Hasher},
28 path::{Path, PathBuf},
29 str::FromStr,
30};
31use sway_utils::DEFAULT_IPFS_GATEWAY_URL;
32
33trait Pin {
35 type Pinned: Fetch + Hash;
36 fn pin(&self, ctx: PinCtx) -> Result<(Self::Pinned, PathBuf)>;
37}
38
39trait Fetch {
41 fn fetch(&self, ctx: PinCtx, local: &Path) -> Result<PackageManifestFile>;
42}
43
44trait DepPath {
46 fn dep_path(&self, name: &str) -> Result<DependencyPath>;
47}
48
49type FetchId = u64;
50
51#[derive(Clone, Debug)]
52pub enum IPFSNode {
53 Local,
54 WithUrl(String),
55}
56
57impl Default for IPFSNode {
58 fn default() -> Self {
59 Self::WithUrl(DEFAULT_IPFS_GATEWAY_URL.to_string())
60 }
61}
62
63impl FromStr for IPFSNode {
64 type Err = anyhow::Error;
65
66 fn from_str(value: &str) -> Result<Self, Self::Err> {
67 match value {
68 "PUBLIC" => {
69 let url = sway_utils::constants::DEFAULT_IPFS_GATEWAY_URL;
70 Ok(IPFSNode::WithUrl(url.to_string()))
71 }
72 "LOCAL" => Ok(IPFSNode::Local),
73 url => Ok(IPFSNode::WithUrl(url.to_string())),
74 }
75 }
76}
77
78#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
86pub enum Source {
87 Member(member::Source),
89 Git(git::Source),
91 Path(path::Source),
93 Ipfs(ipfs::Source),
95 Registry(reg::Source),
97}
98
99#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
104pub enum Pinned {
105 Member(member::Pinned),
106 Git(git::Pinned),
107 Path(path::Pinned),
108 Ipfs(ipfs::Pinned),
109 Registry(reg::Pinned),
110}
111
112#[derive(Clone)]
113pub(crate) struct PinCtx<'a> {
114 pub(crate) fetch_id: FetchId,
118 pub(crate) path_root: PinnedId,
120 pub(crate) offline: bool,
122 pub(crate) name: &'a str,
124 pub(crate) ipfs_node: &'a IPFSNode,
126}
127
128pub(crate) enum DependencyPath {
129 Member,
131 ManifestPath(PathBuf),
133 Root(PinnedId),
135}
136
137pub struct DisplayCompiling<'a, T> {
139 source: &'a T,
140 manifest_dir: &'a Path,
141}
142
143#[derive(Clone, Debug)]
145pub struct PinnedParseError;
146
147impl Source {
148 pub fn from_manifest_dep(
150 manifest_dir: &Path,
151 dep: &manifest::Dependency,
152 member_manifests: &MemberManifestFiles,
153 ) -> Result<Self> {
154 let source = match dep {
155 manifest::Dependency::Simple(ref ver_str) => {
156 bail!(
157 "Unsupported dependency declaration in \"{}\": `{}` - \
158 currently only `git` and `path` dependencies are supported",
159 manifest_dir.display(),
160 ver_str
161 )
162 }
163 manifest::Dependency::Detailed(ref det) => {
164 match (&det.path, &det.version, &det.git, &det.ipfs) {
165 (Some(relative_path), _, _, _) => {
166 let path = manifest_dir.join(relative_path);
167 let canonical_path = path.canonicalize().map_err(|e| {
168 anyhow!("Failed to canonicalize dependency path {:?}: {}", path, e)
169 })?;
170 if member_manifests
172 .values()
173 .any(|pkg_manifest| pkg_manifest.dir() == canonical_path)
174 {
175 Source::Member(member::Source(canonical_path))
176 } else {
177 Source::Path(canonical_path)
178 }
179 }
180 (_, _, Some(repo), _) => {
181 let reference = match (&det.branch, &det.tag, &det.rev) {
182 (Some(branch), None, None) => git::Reference::Branch(branch.clone()),
183 (None, Some(tag), None) => git::Reference::Tag(tag.clone()),
184 (None, None, Some(rev)) => git::Reference::Rev(rev.clone()),
185 (None, None, None) => git::Reference::DefaultBranch,
186 _ => bail!(
187 "git dependencies support at most one reference: \
188 either `branch`, `tag` or `rev`"
189 ),
190 };
191 let repo = Url::from_str(repo)?;
192 let source = git::Source { repo, reference };
193 Source::Git(source)
194 }
195 (_, _, _, Some(ipfs)) => {
196 let cid = ipfs.parse()?;
197 let source = ipfs::Source(cid);
198 Source::Ipfs(source)
199 }
200 _ => {
201 bail!("unsupported set of fields for dependency: {:?}", dep);
202 }
203 }
204 }
205 };
206 Ok(source)
207 }
208
209 pub fn from_manifest_dep_patched(
213 manifest: &PackageManifestFile,
214 dep_name: &str,
215 dep: &manifest::Dependency,
216 members: &MemberManifestFiles,
217 ) -> Result<Self> {
218 let unpatched = Self::from_manifest_dep(manifest.dir(), dep, members)?;
219 unpatched.apply_patch(dep_name, manifest, members)
220 }
221
222 fn dep_patch(
225 &self,
226 dep_name: &str,
227 manifest: &PackageManifestFile,
228 ) -> Result<Option<manifest::Dependency>> {
229 if let Source::Git(git) = self {
230 if let Some(patches) = manifest.resolve_patch(&git.repo.to_string())? {
231 if let Some(patch) = patches.get(dep_name) {
232 return Ok(Some(patch.clone()));
233 }
234 }
235 }
236 Ok(None)
237 }
238
239 pub fn apply_patch(
244 &self,
245 dep_name: &str,
246 manifest: &PackageManifestFile,
247 members: &MemberManifestFiles,
248 ) -> Result<Self> {
249 match self.dep_patch(dep_name, manifest)? {
250 Some(patch) => Self::from_manifest_dep(manifest.dir(), &patch, members),
251 None => Ok(self.clone()),
252 }
253 }
254
255 pub(crate) fn pin(&self, ctx: PinCtx, manifests: &mut ManifestMap) -> Result<Pinned> {
262 fn f<T>(source: &T, ctx: PinCtx, manifests: &mut ManifestMap) -> Result<T::Pinned>
263 where
264 T: Pin,
265 T::Pinned: Clone,
266 Pinned: From<T::Pinned>,
267 {
268 let (pinned, fetch_path) = source.pin(ctx.clone())?;
269 let id = PinnedId::new(ctx.name(), &Pinned::from(pinned.clone()));
270 if let hash_map::Entry::Vacant(entry) = manifests.entry(id) {
271 entry.insert(pinned.fetch(ctx, &fetch_path)?);
272 }
273 Ok(pinned)
274 }
275 match self {
276 Source::Member(source) => Ok(Pinned::Member(f(source, ctx, manifests)?)),
277 Source::Path(source) => Ok(Pinned::Path(f(source, ctx, manifests)?)),
278 Source::Git(source) => Ok(Pinned::Git(f(source, ctx, manifests)?)),
279 Source::Ipfs(source) => Ok(Pinned::Ipfs(f(source, ctx, manifests)?)),
280 Source::Registry(source) => Ok(Pinned::Registry(f(source, ctx, manifests)?)),
281 }
282 }
283}
284
285impl Pinned {
286 pub(crate) const MEMBER: Self = Self::Member(member::Pinned);
287
288 pub(crate) fn dep_path(&self, name: &str) -> Result<DependencyPath> {
290 match self {
291 Self::Member(pinned) => pinned.dep_path(name),
292 Self::Path(pinned) => pinned.dep_path(name),
293 Self::Git(pinned) => pinned.dep_path(name),
294 Self::Ipfs(pinned) => pinned.dep_path(name),
295 Self::Registry(pinned) => pinned.dep_path(name),
296 }
297 }
298
299 pub fn semver(&self) -> Option<semver::Version> {
303 match self {
304 Self::Registry(reg) => Some(reg.source.version.clone()),
305 _ => None,
306 }
307 }
308
309 pub fn display_compiling<'a>(&'a self, manifest_dir: &'a Path) -> DisplayCompiling<'a, Self> {
316 DisplayCompiling {
317 source: self,
318 manifest_dir,
319 }
320 }
321
322 pub fn unpinned(&self, path: &Path) -> Source {
324 match self {
325 Self::Member(_) => Source::Member(member::Source(path.to_owned())),
326 Self::Git(git) => Source::Git(git.source.clone()),
327 Self::Path(_) => Source::Path(path.to_owned()),
328 Self::Ipfs(ipfs) => Source::Ipfs(ipfs::Source(ipfs.0.clone())),
329 Self::Registry(reg) => Source::Registry(reg.source.clone()),
330 }
331 }
332}
333
334impl<'a> PinCtx<'a> {
335 fn fetch_id(&self) -> FetchId {
336 self.fetch_id
337 }
338 fn path_root(&self) -> PinnedId {
339 self.path_root
340 }
341 fn offline(&self) -> bool {
342 self.offline
343 }
344 fn name(&self) -> &str {
345 self.name
346 }
347 fn ipfs_node(&self) -> &'a IPFSNode {
348 self.ipfs_node
349 }
350}
351
352impl fmt::Display for Pinned {
353 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354 match self {
355 Self::Member(src) => src.fmt(f),
356 Self::Path(src) => src.fmt(f),
357 Self::Git(src) => src.fmt(f),
358 Self::Ipfs(src) => src.fmt(f),
359 Self::Registry(_reg) => todo!("pkg registries not yet implemented"),
360 }
361 }
362}
363
364impl fmt::Display for DisplayCompiling<'_, Pinned> {
365 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
366 match self.source {
367 Pinned::Member(_) => self.manifest_dir.display().fmt(f),
368 Pinned::Path(_src) => self.manifest_dir.display().fmt(f),
369 Pinned::Git(src) => src.fmt(f),
370 Pinned::Ipfs(src) => src.fmt(f),
371 Pinned::Registry(_src) => todo!("registry dependencies not yet implemented"),
372 }
373 }
374}
375
376impl FromStr for Pinned {
377 type Err = PinnedParseError;
378 fn from_str(s: &str) -> Result<Self, Self::Err> {
379 let source = if s == "root" || s == "member" {
382 Self::Member(member::Pinned)
383 } else if let Ok(src) = path::Pinned::from_str(s) {
384 Self::Path(src)
385 } else if let Ok(src) = git::Pinned::from_str(s) {
386 Self::Git(src)
387 } else if let Ok(src) = ipfs::Pinned::from_str(s) {
388 Self::Ipfs(src)
389 } else {
390 return Err(PinnedParseError);
392 };
393 Ok(source)
394 }
395}
396
397pub fn fetch_id(path: &Path, timestamp: std::time::Instant) -> u64 {
402 let mut hasher = hash_map::DefaultHasher::new();
403 path.hash(&mut hasher);
404 timestamp.hash(&mut hasher);
405 hasher.finish()
406}