1pub mod git;
11pub(crate) mod ipfs;
12mod member;
13pub mod path;
14pub mod 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, DEFAULT_REGISTRY_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, PartialEq, Eq, PartialOrd, Ord)]
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 IPFSNode {
64 pub fn fuel() -> Self {
66 Self::WithUrl(DEFAULT_REGISTRY_IPFS_GATEWAY_URL.to_string())
67 }
68
69 pub fn public() -> Self {
71 Self::WithUrl(DEFAULT_IPFS_GATEWAY_URL.to_string())
72 }
73}
74
75impl FromStr for IPFSNode {
76 type Err = anyhow::Error;
77
78 fn from_str(value: &str) -> Result<Self, Self::Err> {
79 match value {
80 "PUBLIC" => {
81 let url = sway_utils::constants::DEFAULT_IPFS_GATEWAY_URL;
82 Ok(IPFSNode::WithUrl(url.to_string()))
83 }
84 "FUEL" => {
85 let url = sway_utils::constants::DEFAULT_REGISTRY_IPFS_GATEWAY_URL;
86 Ok(IPFSNode::WithUrl(url.to_string()))
87 }
88 "LOCAL" => Ok(IPFSNode::Local),
89 url => Ok(IPFSNode::WithUrl(url.to_string())),
90 }
91 }
92}
93
94#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
102pub enum Source {
103 Member(member::Source),
105 Git(git::Source),
107 Path(path::Source),
109 Ipfs(ipfs::Source),
111 Registry(reg::Source),
113}
114
115#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
120pub enum Pinned {
121 Member(member::Pinned),
122 Git(git::Pinned),
123 Path(path::Pinned),
124 Ipfs(ipfs::Pinned),
125 Registry(reg::Pinned),
126}
127
128#[derive(Clone)]
129pub(crate) struct PinCtx<'a> {
130 pub(crate) fetch_id: FetchId,
134 pub(crate) path_root: PinnedId,
136 pub(crate) offline: bool,
138 pub(crate) name: &'a str,
140 pub(crate) ipfs_node: &'a IPFSNode,
142}
143
144pub(crate) enum DependencyPath {
145 Member,
147 ManifestPath(PathBuf),
149 Root(PinnedId),
151}
152
153pub struct DisplayCompiling<'a, T> {
155 source: &'a T,
156 manifest_dir: &'a Path,
157}
158
159#[derive(Clone, Debug)]
161pub struct PinnedParseError;
162
163impl Source {
164 fn with_path_dependency(
166 relative_path: &Path,
167 manifest_dir: &Path,
168 member_manifests: &MemberManifestFiles,
169 ) -> Result<Self> {
170 let path = manifest_dir.join(relative_path);
171 let canonical_path = path
172 .canonicalize()
173 .map_err(|e| anyhow!("Failed to canonicalize dependency path {:?}: {}", path, e))?;
174 if member_manifests
176 .values()
177 .any(|pkg_manifest| pkg_manifest.dir() == canonical_path)
178 {
179 Ok(Source::Member(member::Source(canonical_path)))
180 } else {
181 Ok(Source::Path(canonical_path))
182 }
183 }
184
185 fn with_version_dependency(
187 pkg_name: &str,
188 version: &str,
189 namespace: ®::file_location::Namespace,
190 ) -> Result<Self> {
191 let semver = semver::Version::parse(version)?;
194 let source = reg::Source {
195 version: semver,
196 namespace: namespace.clone(),
197 name: pkg_name.to_string(),
198 };
199 Ok(Source::Registry(source))
200 }
201
202 pub fn from_manifest_dep(
204 manifest_dir: &Path,
205 dep_name: &str,
206 dep: &manifest::Dependency,
207 member_manifests: &MemberManifestFiles,
208 ) -> Result<Self> {
209 let source = match dep {
210 manifest::Dependency::Simple(ref ver_str) => Source::with_version_dependency(
211 dep_name,
212 ver_str,
213 ®::file_location::Namespace::Flat,
214 )?,
215 manifest::Dependency::Detailed(ref det) => {
216 match (&det.path, &det.version, &det.git, &det.ipfs) {
217 (Some(relative_path), _, _, _) => {
218 let relative_path = PathBuf::from_str(relative_path)?;
219 Source::with_path_dependency(
220 &relative_path,
221 manifest_dir,
222 member_manifests,
223 )?
224 }
225 (_, _, Some(repo), _) => {
226 let reference = match (&det.branch, &det.tag, &det.rev) {
227 (Some(branch), None, None) => git::Reference::Branch(branch.clone()),
228 (None, Some(tag), None) => git::Reference::Tag(tag.clone()),
229 (None, None, Some(rev)) => git::Reference::Rev(rev.clone()),
230 (None, None, None) => git::Reference::DefaultBranch,
231 _ => bail!(
232 "git dependencies support at most one reference: \
233 either `branch`, `tag` or `rev`"
234 ),
235 };
236 let repo = Url::from_str(repo)?;
237 let source = git::Source { repo, reference };
238 Source::Git(source)
239 }
240 (_, _, _, Some(ipfs)) => {
241 let cid = ipfs.parse()?;
242 let source = ipfs::Source(cid);
243 Source::Ipfs(source)
244 }
245 (None, Some(version), _, _) => {
246 let namespace = det.namespace.as_ref().map_or_else(
247 || reg::file_location::Namespace::Flat,
248 |ns| reg::file_location::Namespace::Domain(ns.to_string()),
249 );
250 Source::with_version_dependency(dep_name, version, &namespace)?
251 }
252 _ => {
253 bail!("unsupported set of fields for dependency: {:?}", dep);
254 }
255 }
256 }
257 };
258 Ok(source)
259 }
260
261 pub fn from_manifest_dep_patched(
265 manifest: &PackageManifestFile,
266 dep_name: &str,
267 dep: &manifest::Dependency,
268 members: &MemberManifestFiles,
269 ) -> Result<Self> {
270 let unpatched = Self::from_manifest_dep(manifest.dir(), dep_name, dep, members)?;
271 unpatched.apply_patch(dep_name, manifest, members)
272 }
273
274 fn dep_patch(
277 &self,
278 dep_name: &str,
279 manifest: &PackageManifestFile,
280 ) -> Result<Option<manifest::Dependency>> {
281 if let Source::Git(git) = self {
282 if let Some(patches) = manifest.resolve_patch(&git.repo.to_string())? {
283 if let Some(patch) = patches.get(dep_name) {
284 return Ok(Some(patch.clone()));
285 }
286 }
287 }
288 Ok(None)
289 }
290
291 pub fn apply_patch(
296 &self,
297 dep_name: &str,
298 manifest: &PackageManifestFile,
299 members: &MemberManifestFiles,
300 ) -> Result<Self> {
301 match self.dep_patch(dep_name, manifest)? {
302 Some(patch) => Self::from_manifest_dep(manifest.dir(), dep_name, &patch, members),
303 None => Ok(self.clone()),
304 }
305 }
306
307 pub(crate) fn pin(&self, ctx: PinCtx, manifests: &mut ManifestMap) -> Result<Pinned> {
314 fn f<T>(source: &T, ctx: PinCtx, manifests: &mut ManifestMap) -> Result<T::Pinned>
315 where
316 T: Pin,
317 T::Pinned: Clone,
318 Pinned: From<T::Pinned>,
319 {
320 let (pinned, fetch_path) = source.pin(ctx.clone())?;
321 let id = PinnedId::new(ctx.name(), &Pinned::from(pinned.clone()));
322 if let hash_map::Entry::Vacant(entry) = manifests.entry(id) {
323 entry.insert(pinned.fetch(ctx, &fetch_path)?);
324 }
325 Ok(pinned)
326 }
327 match self {
328 Source::Member(source) => Ok(Pinned::Member(f(source, ctx, manifests)?)),
329 Source::Path(source) => Ok(Pinned::Path(f(source, ctx, manifests)?)),
330 Source::Git(source) => Ok(Pinned::Git(f(source, ctx, manifests)?)),
331 Source::Ipfs(source) => Ok(Pinned::Ipfs(f(source, ctx, manifests)?)),
332 Source::Registry(source) => Ok(Pinned::Registry(f(source, ctx, manifests)?)),
333 }
334 }
335}
336
337impl Pinned {
338 pub(crate) const MEMBER: Self = Self::Member(member::Pinned);
339
340 pub(crate) fn dep_path(&self, name: &str) -> Result<DependencyPath> {
342 match self {
343 Self::Member(pinned) => pinned.dep_path(name),
344 Self::Path(pinned) => pinned.dep_path(name),
345 Self::Git(pinned) => pinned.dep_path(name),
346 Self::Ipfs(pinned) => pinned.dep_path(name),
347 Self::Registry(pinned) => pinned.dep_path(name),
348 }
349 }
350
351 pub fn semver(&self) -> Option<semver::Version> {
355 match self {
356 Self::Registry(reg) => Some(reg.source.version.clone()),
357 _ => None,
358 }
359 }
360
361 pub fn display_compiling<'a>(&'a self, manifest_dir: &'a Path) -> DisplayCompiling<'a, Self> {
368 DisplayCompiling {
369 source: self,
370 manifest_dir,
371 }
372 }
373
374 pub fn unpinned(&self, path: &Path) -> Source {
376 match self {
377 Self::Member(_) => Source::Member(member::Source(path.to_owned())),
378 Self::Git(git) => Source::Git(git.source.clone()),
379 Self::Path(_) => Source::Path(path.to_owned()),
380 Self::Ipfs(ipfs) => Source::Ipfs(ipfs::Source(ipfs.0.clone())),
381 Self::Registry(reg) => Source::Registry(reg.source.clone()),
382 }
383 }
384}
385
386impl<'a> PinCtx<'a> {
387 fn fetch_id(&self) -> FetchId {
388 self.fetch_id
389 }
390 fn path_root(&self) -> PinnedId {
391 self.path_root
392 }
393 fn offline(&self) -> bool {
394 self.offline
395 }
396 fn name(&self) -> &str {
397 self.name
398 }
399 fn ipfs_node(&self) -> &'a IPFSNode {
400 self.ipfs_node
401 }
402}
403
404impl fmt::Display for Pinned {
405 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406 match self {
407 Self::Member(src) => src.fmt(f),
408 Self::Path(src) => src.fmt(f),
409 Self::Git(src) => src.fmt(f),
410 Self::Ipfs(src) => src.fmt(f),
411 Self::Registry(src) => src.fmt(f),
412 }
413 }
414}
415
416impl fmt::Display for DisplayCompiling<'_, Pinned> {
417 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
418 match self.source {
419 Pinned::Member(_) => self.manifest_dir.display().fmt(f),
420 Pinned::Path(_src) => self.manifest_dir.display().fmt(f),
421 Pinned::Git(src) => src.fmt(f),
422 Pinned::Ipfs(src) => src.fmt(f),
423 Pinned::Registry(src) => src.fmt(f),
424 }
425 }
426}
427
428impl FromStr for Pinned {
429 type Err = PinnedParseError;
430 fn from_str(s: &str) -> Result<Self, Self::Err> {
431 let source = if s == "root" || s == "member" {
434 Self::Member(member::Pinned)
435 } else if let Ok(src) = path::Pinned::from_str(s) {
436 Self::Path(src)
437 } else if let Ok(src) = git::Pinned::from_str(s) {
438 Self::Git(src)
439 } else if let Ok(src) = ipfs::Pinned::from_str(s) {
440 Self::Ipfs(src)
441 } else if let Ok(src) = reg::Pinned::from_str(s) {
442 Self::Registry(src)
443 } else {
444 return Err(PinnedParseError);
445 };
446 Ok(source)
447 }
448}
449
450pub fn fetch_id(path: &Path, timestamp: std::time::Instant) -> u64 {
455 let mut hasher = hash_map::DefaultHasher::new();
456 path.hash(&mut hasher);
457 timestamp.hash(&mut hasher);
458 hasher.finish()
459}