op_primitives/
monorepo.rs

1use eyre::Result;
2use serde::{Deserialize, Serialize};
3use std::{
4    path::{Path, PathBuf},
5    process::Command,
6};
7
8/// A macro to convert a [PathBuf] into a [Result<String>],
9/// returning an error if the path cannot be converted to a string.
10#[macro_export]
11macro_rules! path_to_str {
12    ($path:expr) => {
13        $path
14            .to_str()
15            .ok_or_else(|| eyre::eyre!("Failed to convert path to string: {:?}", $path))
16    };
17}
18
19/// Optimism Monorepo configuration.
20#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
21pub struct MonorepoConfig {
22    /// The name of the directory in which to store the Optimism Monorepo.
23    pub directory_name: String,
24    /// The source from which to obtain the Optimism Monorepo.
25    pub source: MonorepoSource,
26    /// The git URL from which to clone the Optimism Monorepo.
27    pub git_url: String,
28    /// The URL from which to download the Optimism Monorepo tarball.
29    pub tarball_url: String,
30    /// Optionally force overwriting local monorepo artifacts.
31    pub force: bool,
32}
33
34impl Default for MonorepoConfig {
35    fn default() -> Self {
36        Self {
37            source: MonorepoSource::Git,
38            directory_name: "optimism".to_string(),
39            git_url: "git@github.com:ethereum-optimism/optimism.git".to_string(),
40            tarball_url: "https://github.com/ethereum-optimism/optimism/archive/develop.tar.gz"
41                .to_string(),
42            force: false,
43        }
44    }
45}
46
47/// The source from which to obtain the monorepo.
48#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum MonorepoSource {
51    /// Clone from git.
52    #[default]
53    Git,
54    /// Download from a tarball archive.
55    Tarball,
56}
57
58/// The Optimism Monorepo.
59#[derive(Debug, Default, Clone, PartialEq)]
60pub struct Monorepo {
61    /// Path for the directory holding the monorepo dir.
62    pwd: PathBuf,
63    /// Configuration for the monorepo.
64    config: MonorepoConfig,
65}
66
67impl Monorepo {
68    /// Creates a new monorepo instance with the given [MonorepoConfig] options.
69    ///
70    /// # Errors
71    ///
72    /// If the current working directory cannot be determined, this method will return an error.
73    pub fn with_config(config: MonorepoConfig) -> Result<Self> {
74        Ok(Self {
75            pwd: std::env::current_dir()?,
76            config,
77        })
78    }
79
80    /// Returns the path to the monorepo directory.
81    pub fn path(&self) -> PathBuf {
82        self.pwd.join(&self.config.directory_name)
83    }
84
85    /// Returns the devnet artifacts directory.
86    pub fn devnet(&self) -> PathBuf {
87        self.path().join(".devnet")
88    }
89
90    /// Returns the L1 genesis file.
91    pub fn l1_genesis(&self) -> PathBuf {
92        self.devnet().join("genesis-l1.json")
93    }
94
95    /// Returns the L2 genesis file.
96    pub fn l2_genesis(&self) -> PathBuf {
97        self.devnet().join("genesis-l2.json")
98    }
99
100    /// Contracts directory.
101    pub fn contracts(&self) -> PathBuf {
102        self.path().join("packages/contracts-bedrock")
103    }
104
105    /// Deploy config file.
106    pub fn deploy_config(&self) -> PathBuf {
107        self.contracts().join("deploy-config/devnetL1.json")
108    }
109
110    /// Deployments directory.
111    pub fn deployments(&self) -> PathBuf {
112        self.contracts().join("deployments")
113    }
114
115    /// Devnet Deployments directory.
116    pub fn devnet_deploys(&self) -> PathBuf {
117        self.deployments().join("devnetL1")
118    }
119
120    /// Allocs file.
121    pub fn allocs(&self) -> PathBuf {
122        self.devnet().join("allocs-l1.json")
123    }
124
125    /// Addresses json file (the l1 deployments).
126    pub fn addresses_json(&self) -> PathBuf {
127        self.devnet().join("addresses.json")
128    }
129
130    /// Returns the op node directory.
131    pub fn op_node_dir(&self) -> PathBuf {
132        self.path().join("op-node")
133    }
134
135    /// Returns the genesis rollup file.
136    pub fn genesis_rollup(&self) -> PathBuf {
137        self.devnet().join("rollup.json")
138    }
139}
140
141impl Monorepo {
142    /// Obtains the monorepo from the given source.
143    ///
144    /// If the monorepo already exists, this method will garacefully log a warning and return.
145    pub fn obtain_from_source(&self) -> Result<()> {
146        if self.path().exists() && !self.config.force {
147            tracing::warn!(target: "monorepo", "Monorepo already exists, skipping...");
148            return Ok(());
149        }
150
151        match self.config.source {
152            MonorepoSource::Git => self.git_clone(),
153            MonorepoSource::Tarball => self.download(),
154        }
155    }
156
157    /// Clones the Optimism Monorepo into the given directory.
158    fn git_clone(&self) -> Result<()> {
159        tracing::info!(target: "monorepo", "Cloning optimism monorepo (this may take a while)...");
160        git_clone(&self.pwd, &self.config.git_url)
161    }
162
163    /// Downloads the Optimism Monorepo from the configured tarball archive.
164    ///
165    /// # Errors
166    ///
167    /// This function will return an Error if:
168    /// - The archive cannot be downloaded
169    /// - The downloaded file cannot be uncompressed
170    /// - The resulting directory is not found or cannot be moved
171    /// - The archive file cannot be deleted
172    fn download(&self) -> Result<()> {
173        tracing::info!(target: "monorepo", "Downloading optimism monorepo...");
174        let archive_file_name = "optimism_monorepo.tar.gz";
175
176        download_file(&self.pwd, &self.config.tarball_url, archive_file_name)?;
177        unzip_tarball(&self.pwd, archive_file_name)?;
178        mv_dir(&self.pwd, "optimism-develop", &self.config.directory_name)?;
179        std::fs::remove_file(archive_file_name)?;
180        Ok(())
181    }
182}
183
184/// Clones a given git repository into the given directory.
185pub(crate) fn git_clone(pwd: &Path, repo: &str) -> Result<()> {
186    let out = Command::new("git")
187        .arg("clone")
188        .arg("--recursive")
189        .arg("--depth")
190        .arg("1")
191        .arg(repo)
192        .current_dir(pwd)
193        .output()?;
194    if !out.status.success() {
195        eyre::bail!(
196            "Failed to clone {} in {:?}: {}",
197            repo,
198            pwd,
199            String::from_utf8_lossy(&out.stderr)
200        )
201    }
202
203    Ok(())
204}
205
206/// Downloads a file from a given URL into the given directory.
207pub(crate) fn download_file(pwd: &Path, url: &str, name: &str) -> Result<()> {
208    let out = Command::new("curl")
209        .arg("-L")
210        .arg("--output")
211        .arg(name)
212        .arg(url)
213        .current_dir(pwd)
214        .output()?;
215    if !out.status.success() {
216        eyre::bail!(
217            "Failed to download {} in {:?}: {}",
218            url,
219            pwd,
220            String::from_utf8_lossy(&out.stderr)
221        )
222    }
223
224    Ok(())
225}
226
227/// Unzips a tarball archive into the given directory.
228pub(crate) fn unzip_tarball(pwd: &Path, name: &str) -> Result<()> {
229    let out = Command::new("tar")
230        .arg("-xvf")
231        .arg(name)
232        .current_dir(pwd)
233        .output()?;
234    if !out.status.success() {
235        eyre::bail!(
236            "Failed to unzip {} in {:?}: {}",
237            name,
238            pwd,
239            String::from_utf8_lossy(&out.stderr)
240        )
241    }
242
243    Ok(())
244}
245
246/// Moves a directory from one location to another.
247pub(crate) fn mv_dir(pwd: &Path, src: &str, dst: &str) -> Result<()> {
248    let out = Command::new("mv")
249        .arg(src)
250        .arg(dst)
251        .current_dir(pwd)
252        .output()?;
253    if !out.status.success() {
254        eyre::bail!(
255            "Failed to move {} to {} in {:?}: {}",
256            src,
257            dst,
258            pwd,
259            String::from_utf8_lossy(&out.stderr)
260        )
261    }
262
263    Ok(())
264}