1use crate::Error;
4use duct::cmd;
5use pop_common::{Profile, manifest::from_path};
6pub use srtool_lib::{ContainerEngine, get_image_digest, get_image_tag};
7use std::{
8 env, fs,
9 path::{Path, PathBuf},
10};
11
12const DEFAULT_IMAGE: &str = "docker.io/paritytech/srtool";
13const TIMEOUT: u64 = 60 * 60;
14
15pub struct DeterministicBuilder {
18 cache_mount: String,
20 default_features: String,
22 digest: String,
24 engine: ContainerEngine,
26 image: String,
28 package: String,
30 path: PathBuf,
32 profile: Profile,
34 runtime_dir: PathBuf,
36 tag: String,
38}
39
40impl DeterministicBuilder {
41 pub fn new(
50 engine: ContainerEngine,
51 path: Option<PathBuf>,
52 package: &str,
53 profile: Profile,
54 runtime_dir: PathBuf,
55 ) -> Result<Self, Error> {
56 let default_features = String::new();
57 let tag = get_image_tag(Some(TIMEOUT)).map_err(|_| Error::ImageTagRetrievalFailed)?;
58 let digest = get_image_digest(DEFAULT_IMAGE, &tag).unwrap_or_default();
59 let dir = fs::canonicalize(path.unwrap_or_else(|| PathBuf::from("./")))?;
60 let tmpdir = env::temp_dir().join("cargo");
61
62 let no_cache = engine == ContainerEngine::Podman;
63 let cache_mount =
64 if !no_cache { format!("-v {}:/cargo-home", tmpdir.display()) } else { String::new() };
65
66 Ok(Self {
67 cache_mount,
68 default_features,
69 digest,
70 engine,
71 image: DEFAULT_IMAGE.to_string(),
72 package: package.to_owned(),
73 path: dir,
74 profile,
75 runtime_dir,
76 tag,
77 })
78 }
79
80 pub fn build(&self) -> Result<PathBuf, Error> {
82 let command = self.build_command();
83 cmd("sh", vec!["-c", &command]).stdout_null().stderr_null().run()?;
84 let wasm_path = self.get_output_path();
85 Ok(wasm_path)
86 }
87
88 fn build_command(&self) -> String {
90 format!(
91 "{} run --name srtool --rm \
92 -e PACKAGE={} \
93 -e RUNTIME_DIR={} \
94 -e DEFAULT_FEATURES={} \
95 -e PROFILE={} \
96 -e IMAGE={} \
97 -v {}:/build \
98 {} \
99 {}:{} build --app --json",
100 self.engine,
101 self.package,
102 self.runtime_dir.display(),
103 self.default_features,
104 self.profile,
105 self.digest,
106 self.path.display(),
107 self.cache_mount,
108 self.image,
109 self.tag
110 )
111 }
112
113 fn get_output_path(&self) -> PathBuf {
115 self.runtime_dir
116 .join("target")
117 .join("srtool")
118 .join(self.profile.to_string())
119 .join("wbuild")
120 .join(&self.package)
121 .join(format!("{}.compact.compressed.wasm", self.package.replace("-", "_")))
122 }
123}
124
125pub fn is_supported(path: &Path) -> bool {
131 let manifest = match from_path(path) {
132 Ok(m) => m,
133 Err(_) => return false,
134 };
135 const DEPENDENCIES: [&str; 3] = ["frame-system", "frame-support", "substrate-wasm-builder"];
137 let has_dependencies = DEPENDENCIES.into_iter().any(|d| {
138 manifest.dependencies.contains_key(d) ||
139 manifest.workspace.as_ref().is_some_and(|w| w.dependencies.contains_key(d))
140 });
141 let has_features = manifest.features.contains_key("runtime-benchmarks") ||
142 manifest.features.contains_key("try-runtime");
143 has_dependencies && has_features
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use anyhow::Result;
150 use fs::write;
151 use pop_common::manifest::Dependency;
152 use tempfile::tempdir;
153
154 #[test]
155 fn srtool_builder_new_works() -> Result<()> {
156 let srtool_builer = DeterministicBuilder::new(
157 ContainerEngine::Docker,
158 None,
159 "parachain-template-runtime",
160 Profile::Release,
161 PathBuf::from("./runtime"),
162 )?;
163 assert_eq!(
164 srtool_builer.cache_mount,
165 format!("-v {}:/cargo-home", env::temp_dir().join("cargo").display())
166 );
167 assert_eq!(srtool_builer.default_features, "");
168
169 let tag = get_image_tag(Some(TIMEOUT))?;
170 let digest = get_image_digest(DEFAULT_IMAGE, &tag).unwrap_or_default();
171 assert_eq!(srtool_builer.digest, digest);
172 assert_eq!(srtool_builer.tag, tag);
173
174 assert!(srtool_builer.engine == ContainerEngine::Docker);
175 assert_eq!(srtool_builer.image, DEFAULT_IMAGE);
176 assert_eq!(srtool_builer.package, "parachain-template-runtime");
177 assert_eq!(srtool_builer.path, fs::canonicalize(PathBuf::from("./"))?);
178 assert_eq!(srtool_builer.profile, Profile::Release);
179 assert_eq!(srtool_builer.runtime_dir, PathBuf::from("./runtime"));
180
181 Ok(())
182 }
183
184 #[test]
185 fn build_command_works() -> Result<()> {
186 let temp_dir = tempdir()?;
187 let path = temp_dir.path();
188 let tag = get_image_tag(Some(TIMEOUT))?;
189 let digest = get_image_digest(DEFAULT_IMAGE, &tag).unwrap_or_default();
190 assert_eq!(
191 DeterministicBuilder::new(
192 ContainerEngine::Podman,
193 Some(path.to_path_buf()),
194 "parachain-template-runtime",
195 Profile::Production,
196 PathBuf::from("./runtime"),
197 )?
198 .build_command(),
199 format!(
200 "podman run --name srtool --rm \
201 -e PACKAGE=parachain-template-runtime \
202 -e RUNTIME_DIR=./runtime \
203 -e DEFAULT_FEATURES= \
204 -e PROFILE=production \
205 -e IMAGE={} \
206 -v {}:/build \
207 {} \
208 {}:{} build --app --json",
209 digest,
210 fs::canonicalize(path)?.display(),
211 String::new(),
212 DEFAULT_IMAGE,
213 tag
214 )
215 );
216 Ok(())
217 }
218
219 #[test]
220 fn get_output_path_works() -> Result<()> {
221 let srtool_builder = DeterministicBuilder::new(
222 ContainerEngine::Podman,
223 None,
224 "template-runtime",
225 Profile::Debug,
226 PathBuf::from("./runtime-folder"),
227 )?;
228 assert_eq!(
229 srtool_builder.get_output_path().display().to_string(),
230 "./runtime-folder/target/srtool/debug/wbuild/template-runtime/template_runtime.compact.compressed.wasm"
231 );
232 Ok(())
233 }
234
235 #[test]
236 fn is_supported_works() -> Result<()> {
237 let temp_dir = tempdir()?;
238 let path = temp_dir.path();
239
240 let name = "hello_world";
242 cmd("cargo", ["new", name]).dir(path).run()?;
243 assert!(!is_supported(&path.join(name)));
244
245 let mut manifest = from_path(&path.join(name))?;
247 manifest
248 .dependencies
249 .insert("substrate-wasm-builder".into(), Dependency::Simple("^0.14.0".into()));
250 manifest.features.insert("try-runtime".into(), vec![]);
251 let manifest = toml_edit::ser::to_string_pretty(&manifest)?;
252 write(path.join(name).join("Cargo.toml"), manifest)?;
253 assert!(is_supported(&path.join(name)));
254 Ok(())
255 }
256}