Skip to main content

soldeer_core/lock/
forge.rs

1//! Vendored version of the `lockfile` module of `forge`.
2//!
3//! Slightly adapted to reduce dependencies.
4
5use log::debug;
6use serde::{Deserialize, Serialize};
7use std::{
8    collections::HashMap,
9    fs,
10    path::{Path, PathBuf},
11};
12
13use crate::errors::LockError;
14
15use super::Result;
16
17pub const FOUNDRY_LOCK: &str = "foundry.lock";
18
19/// A type alias for a HashMap of dependencies keyed by relative path to the submodule dir.
20pub type DepMap = HashMap<PathBuf, DepIdentifier>;
21
22/// A lockfile handler that keeps track of the dependencies and their current state.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Lockfile {
25    /// A map of the dependencies keyed by relative path to the submodule dir.
26    #[serde(flatten)]
27    deps: DepMap,
28    /// Absolute path to the lockfile.
29    #[serde(skip)]
30    lockfile_path: PathBuf,
31}
32
33impl Lockfile {
34    /// Create a new [`Lockfile`] instance.
35    ///
36    /// `project_root` is the absolute path to the project root.
37    ///
38    /// You will need to call [`Lockfile::read`] to load the lockfile.
39    pub fn new(project_root: &Path) -> Self {
40        Self { deps: HashMap::default(), lockfile_path: project_root.join(FOUNDRY_LOCK) }
41    }
42
43    /// Loads the lockfile from the project root.
44    ///
45    /// Throws an error if the lockfile does not exist.
46    pub fn read(&mut self) -> Result<()> {
47        if !self.lockfile_path.exists() {
48            return Err(LockError::FoundryLockMissing);
49        }
50
51        let lockfile_str = fs::read_to_string(&self.lockfile_path)?;
52
53        self.deps = serde_json::from_str(&lockfile_str)?;
54
55        debug!(lockfile:? = self.deps; "loaded lockfile");
56
57        Ok(())
58    }
59
60    /// Get the [`DepIdentifier`] for a submodule at a given path.
61    pub fn get(&self, path: &Path) -> Option<&DepIdentifier> {
62        self.deps.get(path)
63    }
64
65    /// Returns the num of dependencies in the lockfile.
66    pub fn len(&self) -> usize {
67        self.deps.len()
68    }
69
70    /// Returns whether the lockfile is empty.
71    pub fn is_empty(&self) -> bool {
72        self.deps.is_empty()
73    }
74
75    /// Returns an iterator over the lockfile.
76    pub fn iter(&self) -> impl Iterator<Item = (&PathBuf, &DepIdentifier)> {
77        self.deps.iter()
78    }
79
80    pub fn exists(&self) -> bool {
81        self.lockfile_path.exists()
82    }
83}
84
85// Implement .iter() for &LockFile
86
87/// Identifies whether a dependency (submodule) is referenced by a branch,
88/// tag or rev (commit hash).
89///
90/// Each enum variant consists of an `r#override` flag which is used in `forge update` to decide
91/// whether to update a dep or not. This flag is skipped during serialization.
92#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
93pub enum DepIdentifier {
94    /// `name` of the branch and the `rev` it is currently pointing to.
95    #[serde(rename = "branch")]
96    Branch { name: String, rev: String },
97
98    /// Release tag `name` and the `rev` it is currently pointing to.
99    #[serde(rename = "tag")]
100    Tag { name: String, rev: String },
101
102    /// Commit hash `rev` the submodule is currently pointing to.
103    #[serde(rename = "rev", untagged)]
104    Rev { rev: String },
105}
106
107impl DepIdentifier {
108    /// Get the commit hash of the dependency.
109    pub fn rev(&self) -> &str {
110        match self {
111            Self::Branch { rev, .. } => rev,
112            Self::Tag { rev, .. } => rev,
113            Self::Rev { rev, .. } => rev,
114        }
115    }
116
117    /// Get the name of the dependency.
118    ///
119    /// In case of a Rev, this will return the commit hash.
120    pub fn name(&self) -> &str {
121        match self {
122            Self::Branch { name, .. } => name,
123            Self::Tag { name, .. } => name,
124            Self::Rev { rev, .. } => rev,
125        }
126    }
127
128    /// Get the name/rev to checkout at.
129    pub fn checkout_id(&self) -> &str {
130        match self {
131            Self::Branch { name, .. } => name,
132            Self::Tag { name, .. } => name,
133            Self::Rev { rev, .. } => rev,
134        }
135    }
136
137    /// Returns whether the dependency is a branch.
138    pub fn is_branch(&self) -> bool {
139        matches!(self, Self::Branch { .. })
140    }
141}
142
143impl std::fmt::Display for DepIdentifier {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        match self {
146            Self::Branch { name, rev, .. } => write!(f, "branch={name}@{rev}"),
147            Self::Tag { name, rev, .. } => write!(f, "tag={name}@{rev}"),
148            Self::Rev { rev, .. } => write!(f, "rev={rev}"),
149        }
150    }
151}