reweb3/hardhat/
mod.rs

1//! Utilities that communicate with the hard hat network.
2
3use std::{
4    io::{BufRead, BufReader},
5    path::{Path, PathBuf},
6    process::{Child, Command, Stdio},
7    sync::{Arc, Mutex},
8    thread::spawn,
9};
10
11use crate::errors::{Error, Result};
12
13/// A type represents a hard hat project item.
14pub struct HardHatProject {
15    #[allow(unused)]
16    /// hardhat project root directory path.
17    path: PathBuf,
18    /// The hardhat network node associated with the project.
19    network: Arc<Mutex<RawHardHatNetwork>>,
20}
21
22impl<P: AsRef<Path>> From<P> for HardHatProject {
23    fn from(value: P) -> Self {
24        Self {
25            path: value.as_ref().to_owned(),
26            network: Arc::new(Mutex::new(RawHardHatNetwork {
27                root_path: value.as_ref().to_owned(),
28                child_process: None,
29            })),
30        }
31    }
32}
33
34impl HardHatProject {
35    /// try start hardhat network.
36    ///
37    /// if the network node is already started, returns the [`HardHatNetwork`] instance immediately
38    pub fn start_network(&self) -> Result<HardHatNetwork> {
39        {
40            let mut network = self.network.lock().unwrap();
41
42            network.restart()?;
43        }
44
45        Ok(HardHatNetwork {
46            inner: self.network.clone(),
47        })
48    }
49
50    /// Invoke `hardhat compile` command, and wait to exit.
51    pub fn compile(&self) -> Result<()> {
52        let mut child_process = Command::new("npx")
53            .arg("hardhat")
54            .arg("compile")
55            .current_dir(&self.path)
56            .stdout(Stdio::piped())
57            .spawn()?;
58
59        let stdout = child_process.stdout.take().unwrap();
60
61        let stdout_reader = BufReader::new(stdout);
62        let stdout_lines = stdout_reader.lines();
63
64        for line in stdout_lines {
65            if let Ok(line) = line {
66                println!("{}", line);
67            } else {
68                break;
69            }
70        }
71
72        let status = child_process.wait()?;
73
74        if !status.success() {
75            return Err(Error::Other(format!(
76                "hardhat compile exit with errorcode: {}",
77                status
78            )));
79        }
80
81        Ok(())
82    }
83}
84
85#[derive(Default)]
86struct RawHardHatNetwork {
87    root_path: PathBuf,
88    child_process: Option<Child>,
89}
90
91impl RawHardHatNetwork {
92    fn restart(&mut self) -> Result<()> {
93        self.stop()?;
94
95        log::trace!("start hardhat node in directory: {:#?}", self.root_path);
96
97        let mut child_process = Command::new("npx")
98            .arg("hardhat")
99            .arg("node")
100            .current_dir(&self.root_path)
101            .stdout(Stdio::piped())
102            .spawn()?;
103
104        let stdout = child_process.stdout.take().unwrap();
105
106        spawn(move || {
107            let stdout_reader = BufReader::new(stdout);
108            let stdout_lines = stdout_reader.lines();
109
110            log::trace!("spawn stdout lines");
111
112            for line in stdout_lines {
113                if let Ok(line) = line {
114                    println!("{}", line);
115                } else {
116                    break;
117                }
118            }
119        });
120
121        self.child_process = Some(child_process);
122
123        Ok(())
124    }
125
126    fn stop(&mut self) -> Result<()> {
127        if let Some(mut child_process) = self.child_process.take() {
128            child_process.kill()?;
129            child_process.wait()?;
130        }
131
132        Ok(())
133    }
134}
135
136pub struct HardHatNetwork {
137    inner: Arc<Mutex<RawHardHatNetwork>>,
138}
139
140impl HardHatNetwork {
141    /// Restart the hardhat network child process.
142    pub fn restart(&self) -> Result<()> {
143        self.inner.lock().unwrap().restart()?;
144
145        Ok(())
146    }
147
148    /// Stop the child process
149    pub fn stop(&self) -> Result<()> {
150        self.inner.lock().unwrap().stop()?;
151
152        Ok(())
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use std::{thread::sleep, time::Duration};
159
160    use super::*;
161
162    #[ignore]
163    #[test]
164    fn test_hardhat() {
165        pretty_env_logger::init();
166        let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/hardhat");
167
168        let project = HardHatProject::from(root_path);
169
170        let network = project.start_network().unwrap();
171
172        sleep(Duration::from_secs(2));
173
174        network.stop().unwrap();
175
176        project.compile().unwrap();
177    }
178}