near_sandbox_utils/
lib.rs1use anyhow::{anyhow, Context};
2use binary_install::Cache;
3use fs4::FileExt;
4use tokio::process::{Child, Command};
5
6use std::fs::File;
7use std::path::{Path, PathBuf};
8
9pub mod sync;
10
11pub const DEFAULT_NEAR_SANDBOX_VERSION: &str = "2.9.0";
15
16const fn platform() -> Option<&'static str> {
17 #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
18 return Some("Linux-x86_64");
19
20 #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
22 return None;
23
24 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
25 return Some("Darwin-arm64");
26
27 #[cfg(all(
28 not(target_os = "macos"),
29 not(all(target_os = "linux", target_arch = "x86_64"))
30 ))]
31 return None;
32}
33
34fn local_addr(port: u16) -> String {
35 format!("0.0.0.0:{}", port)
36}
37
38fn bin_url(version: &str) -> Option<String> {
40 if let Ok(val) = std::env::var("SANDBOX_ARTIFACT_URL") {
41 return Some(val);
42 }
43
44 Some(format!(
45 "https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore/{}/{}/near-sandbox.tar.gz",
46 platform()?,
47 version,
48 ))
49}
50
51fn download_path(version: &str) -> PathBuf {
53 #[cfg(feature = "global_install")]
54 let mut out = dirs_next::home_dir().expect("could not retrieve home_dir");
55 #[cfg(not(feature = "global_install"))]
56 let mut out = PathBuf::from(env!("OUT_DIR"));
57
58 out.push(".near");
59 out.push(format!("near-sandbox-{}", normalize_name(version)));
60 if !out.exists() {
61 std::fs::create_dir_all(&out).expect("could not create download path");
62 }
63
64 out
65}
66
67pub fn bin_path(version: &str) -> anyhow::Result<PathBuf> {
69 if let Ok(path) = std::env::var("NEAR_SANDBOX_BIN_PATH") {
70 let path = PathBuf::from(path);
71 if !path.exists() {
72 anyhow::bail!("binary {} does not exist", path.display());
73 }
74 return Ok(path);
75 }
76
77 let mut buf = download_path(version);
78 buf.push("near-sandbox");
79
80 Ok(buf)
81}
82
83fn normalize_name(input: &str) -> String {
84 input.replace('/', "_")
85}
86
87pub fn install_with_version(version: &str) -> anyhow::Result<PathBuf> {
91 if let Some(bin_path) = check_for_version(version)? {
92 return Ok(bin_path);
93 }
94
95 let bin_name = format!("near-sandbox-{}", normalize_name(version));
97 let dl_cache = Cache::at(&download_path(version));
98 let bin_path = bin_url(version).ok_or_else(|| {
99 anyhow!("Unsupported platform: only linux-x86 and darwin-arm are supported")
100 })?;
101 let dl = dl_cache
102 .download(true, &bin_name, &["near-sandbox"], &bin_path)
103 .map_err(anyhow::Error::msg)
104 .with_context(|| "unable to download near-sandbox")?
105 .ok_or_else(|| anyhow!("Could not install near-sandbox"))?;
106
107 let path = dl.binary("near-sandbox").map_err(anyhow::Error::msg)?;
108
109 let dest = download_path(version).join("near-sandbox");
111 std::fs::rename(path, &dest)?;
112
113 Ok(dest)
114}
115
116pub fn install() -> anyhow::Result<PathBuf> {
119 ensure_sandbox_bin_with_version(DEFAULT_NEAR_SANDBOX_VERSION)
120}
121
122fn installable(bin_path: &Path) -> anyhow::Result<Option<std::fs::File>> {
123 if bin_path.exists() {
125 return Ok(None);
126 }
127
128 let mut lockpath = bin_path.to_path_buf();
129 lockpath.set_extension("lock");
130
131 let lockfile = File::create(lockpath)?;
133 lockfile.lock_exclusive()?;
134
135 if bin_path.exists() {
137 Ok(None)
138 } else {
139 Ok(Some(lockfile))
140 }
141}
142
143pub fn ensure_sandbox_bin() -> anyhow::Result<PathBuf> {
144 ensure_sandbox_bin_with_version(DEFAULT_NEAR_SANDBOX_VERSION)
145}
146
147pub fn run_with_options(options: &[&str]) -> anyhow::Result<Child> {
148 let bin_path = crate::ensure_sandbox_bin()?;
149 Command::new(&bin_path)
150 .args(options)
151 .envs(crate::log_vars())
152 .spawn()
153 .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
154}
155
156pub fn run(home_dir: impl AsRef<Path>, rpc_port: u16, network_port: u16) -> anyhow::Result<Child> {
157 #[allow(deprecated)]
158 run_with_version(
159 home_dir,
160 rpc_port,
161 network_port,
162 DEFAULT_NEAR_SANDBOX_VERSION,
163 )
164}
165
166pub fn init(home_dir: impl AsRef<Path>) -> anyhow::Result<Child> {
167 init_with_version(home_dir, DEFAULT_NEAR_SANDBOX_VERSION)
168}
169
170pub fn ensure_sandbox_bin_with_version(version: &str) -> anyhow::Result<PathBuf> {
171 let mut bin_path = bin_path(version)?;
172 if let Some(lockfile) = installable(&bin_path)? {
173 bin_path = install_with_version(version)?;
174 println!("Installed near-sandbox into {}", bin_path.to_str().unwrap());
175 std::env::set_var("NEAR_SANDBOX_BIN_PATH", bin_path.as_os_str());
176 FileExt::unlock(&lockfile).map_err(anyhow::Error::msg)?;
177 }
178
179 Ok(bin_path)
180}
181
182pub fn run_with_options_with_version(options: &[&str], version: &str) -> anyhow::Result<Child> {
183 let bin_path = ensure_sandbox_bin_with_version(version)?;
184 Command::new(&bin_path)
185 .args(options)
186 .envs(crate::log_vars())
187 .spawn()
188 .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
189}
190
191pub fn run_with_version(
192 home_dir: impl AsRef<Path>,
193 rpc_port: u16,
194 network_port: u16,
195 version: &str,
196) -> anyhow::Result<Child> {
197 let home_dir = home_dir.as_ref().to_str().unwrap();
198
199 run_with_options_with_version(
200 &[
201 "--home",
202 home_dir,
203 "run",
204 "--rpc-addr",
205 &local_addr(rpc_port),
206 "--network-addr",
207 &local_addr(network_port),
208 ],
209 version,
210 )
211}
212
213pub fn init_with_version(home_dir: impl AsRef<Path>, version: &str) -> anyhow::Result<Child> {
215 let bin_path = ensure_sandbox_bin_with_version(version)?;
216 let home_dir = home_dir.as_ref().to_str().unwrap();
217 Command::new(&bin_path)
218 .envs(log_vars())
219 .args(["--home", home_dir, "init", "--fast"])
220 .spawn()
221 .with_context(|| format!("failed to init sandbox using '{}'", bin_path.display()))
222}
223
224fn log_vars() -> Vec<(String, String)> {
225 let mut vars = Vec::new();
226 if let Ok(val) = std::env::var("NEAR_SANDBOX_LOG") {
227 vars.push(("RUST_LOG".into(), val));
228 }
229 if let Ok(val) = std::env::var("NEAR_SANDBOX_LOG_STYLE") {
230 vars.push(("RUST_LOG_STYLE".into(), val));
231 }
232 vars
233}
234
235fn check_for_version(version: &str) -> anyhow::Result<Option<PathBuf>> {
239 if let Ok(bin_path) = &std::env::var("NEAR_SANDBOX_BIN_PATH") {
241 return Ok(Some(PathBuf::from(bin_path)));
242 }
243
244 let out_dir = download_path(version).join("near-sandbox");
246 if !out_dir.exists() {
247 return Ok(None);
248 }
249
250 Ok(Some(out_dir))
251}