use crate::Error;
use duct::cmd;
use pop_common::{Docker, Profile, manifest::from_path};
use std::{
env, fs,
path::{Path, PathBuf},
};
const DEFAULT_IMAGE: &str = "docker.io/paritytech/srtool";
const SRTOOL_TAG_URL: &str =
"https://raw.githubusercontent.com/paritytech/srtool/master/RUSTC_VERSION";
pub struct DeterministicBuilder {
cache_mount: String,
default_features: String,
digest: String,
image: String,
package: String,
path: PathBuf,
profile: Profile,
runtime_dir: PathBuf,
tag: String,
}
impl DeterministicBuilder {
pub async fn new(
path: Option<PathBuf>,
package: &str,
profile: Profile,
runtime_dir: PathBuf,
tag: Option<String>,
) -> Result<Self, Error> {
let default_features = String::new();
let tag = match tag {
Some(tag) => tag,
_ => pop_common::docker::fetch_image_tag(SRTOOL_TAG_URL).await?,
};
let digest = Docker::get_image_digest(DEFAULT_IMAGE, &tag).await?;
let dir = fs::canonicalize(path.unwrap_or_else(|| PathBuf::from("./")))?;
let tmpdir = env::temp_dir().join("cargo");
let cache_mount = format!("{}:/cargo-home", tmpdir.display());
Ok(Self {
cache_mount,
default_features,
digest,
image: DEFAULT_IMAGE.to_string(),
package: package.to_owned(),
path: dir,
profile,
runtime_dir,
tag,
})
}
pub fn build(&self) -> Result<PathBuf, Error> {
let args = self.build_args()?;
cmd("docker", args).stdout_null().stderr_null().run()?;
Ok(self.get_output_path())
}
fn build_args(&self) -> Result<Vec<String>, Error> {
let package = format!("PACKAGE={}", self.package);
let absolute_workspace_path = std::fs::canonicalize(
rustilities::manifest::find_workspace_manifest(std::env::current_dir()?)
.ok_or(anyhow::anyhow!("Pop cannot determine your workspace path"))?
.parent()
.expect("A workspace manifest is a file and hence always have a parent; qed;"),
)?;
let runtime_dir = self
.runtime_dir
.strip_prefix(absolute_workspace_path)
.unwrap_or(&self.runtime_dir);
let runtime_dir = format!("RUNTIME_DIR={}", runtime_dir.display());
let default_features = format!("DEFAULT_FEATURES={}", self.default_features);
let profile = match self.profile {
Profile::Debug => "PROFILE=dev".to_owned(),
_ => format!("PROFILE={}", self.profile),
};
let image_digest = format!("IMAGE={}", self.digest);
let volume = format!("{}:/build", self.path.display());
let image_tag = format!("{}:{}", self.image, self.tag);
let args = vec![
"run".to_owned(),
"--name".to_owned(),
"srtool".to_owned(),
"--rm".to_owned(),
"-e".to_owned(),
package,
"-e".to_owned(),
runtime_dir,
"-e".to_owned(),
default_features,
"-e".to_owned(),
profile,
"-e".to_owned(),
image_digest,
"-v".to_owned(),
volume,
"-v".to_owned(),
self.cache_mount.clone(),
image_tag,
"build".to_owned(),
"--app".to_owned(),
"--json".to_owned(),
];
Ok(args)
}
fn get_output_path(&self) -> PathBuf {
let output_wasm = match self.profile {
Profile::Debug => "wasm",
_ => "compact.compressed.wasm",
};
self.runtime_dir
.join("target")
.join("srtool")
.join(self.profile.to_string())
.join("wbuild")
.join(&self.package)
.join(format!("{}.{}", self.package.replace("-", "_"), output_wasm))
}
}
pub fn is_supported(path: &Path) -> bool {
let manifest = match from_path(path) {
Ok(m) => m,
Err(_) => return false,
};
const DEPENDENCIES: [&str; 3] = ["frame-system", "frame-support", "substrate-wasm-builder"];
let has_dependencies = DEPENDENCIES.into_iter().any(|d| {
manifest.dependencies.contains_key(d) ||
manifest.workspace.as_ref().is_some_and(|w| w.dependencies.contains_key(d))
});
let has_features = manifest.features.contains_key("runtime-benchmarks") ||
manifest.features.contains_key("try-runtime");
has_dependencies && has_features
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use fs::write;
use pop_common::manifest::Dependency;
use tempfile::tempdir;
const SRTOOL_TAG: &str = "1.88.0";
const SRTOOL_DIGEST: &str =
"sha256:9902e50293f55fa34bc8d83916aad3fdf9ab3c74f2c0faee6dec8cc705a3a5d7";
#[tokio::test]
async fn srtool_builder_new_works() {
Docker::ensure_running().await.unwrap();
let srtool_builder = DeterministicBuilder::new(
None,
"parachain-template-runtime",
Profile::Release,
PathBuf::from("./runtime"),
Some(SRTOOL_TAG.to_owned()),
)
.await
.unwrap();
assert_eq!(
srtool_builder.cache_mount,
format!("{}:/cargo-home", env::temp_dir().join("cargo").display())
);
assert_eq!(srtool_builder.default_features, "");
assert_eq!(srtool_builder.digest, SRTOOL_DIGEST);
assert_eq!(srtool_builder.tag, SRTOOL_TAG);
assert_eq!(srtool_builder.image, DEFAULT_IMAGE);
assert_eq!(srtool_builder.package, "parachain-template-runtime");
assert_eq!(srtool_builder.path, fs::canonicalize(PathBuf::from("./")).unwrap());
assert_eq!(srtool_builder.profile, Profile::Release);
assert_eq!(srtool_builder.runtime_dir, PathBuf::from("./runtime"));
}
#[tokio::test]
async fn build_args_works() {
Docker::ensure_running().await.unwrap();
let temp_dir = tempdir().unwrap();
let path = temp_dir.path();
assert_eq!(
DeterministicBuilder::new(
Some(path.to_path_buf()),
"parachain-template-runtime",
Profile::Production,
PathBuf::from("./runtime"),
Some(SRTOOL_TAG.to_owned())
)
.await
.unwrap()
.build_args()
.unwrap(),
vec!(
"run",
"--name",
"srtool",
"--rm",
"-e",
"PACKAGE=parachain-template-runtime",
"-e",
"RUNTIME_DIR=./runtime",
"-e",
"DEFAULT_FEATURES=",
"-e",
"PROFILE=production",
"-e",
&format!("IMAGE={SRTOOL_DIGEST}"),
"-v",
&format!("{}:/build", fs::canonicalize(path).unwrap().display()),
"-v",
&format!("{}:/cargo-home", env::temp_dir().join("cargo").display()),
&format!("{DEFAULT_IMAGE}:{SRTOOL_TAG}"),
"build",
"--app",
"--json"
),
);
}
#[tokio::test]
async fn get_output_path_works() -> Result<()> {
Docker::ensure_running().await?;
let srtool_builder = DeterministicBuilder::new(
None,
"template-runtime",
Profile::Debug,
PathBuf::from("./runtime-folder"),
None,
)
.await?;
assert_eq!(
srtool_builder.get_output_path().display().to_string(),
"./runtime-folder/target/srtool/debug/wbuild/template-runtime/template_runtime.wasm"
);
Ok(())
}
#[test]
fn is_supported_works() -> Result<()> {
let temp_dir = tempdir()?;
let path = temp_dir.path();
let name = "hello_world";
cmd("cargo", ["new", name]).dir(path).run()?;
assert!(!is_supported(&path.join(name)));
let mut manifest = from_path(&path.join(name))?;
manifest
.dependencies
.insert("substrate-wasm-builder".into(), Dependency::Simple("^0.14.0".into()));
manifest.features.insert("try-runtime".into(), vec![]);
let manifest = toml_edit::ser::to_string_pretty(&manifest)?;
write(path.join(name).join("Cargo.toml"), manifest)?;
assert!(is_supported(&path.join(name)));
Ok(())
}
}