use std::{env, fs, path::Path};
use cargo_metadata::{
camino::{Utf8Path, Utf8PathBuf},
Metadata, MetadataCommand,
};
use eyre::{eyre, Context, OptionExt};
use sp1_build::BuildArgs;
use super::Mode;
pub struct ProgramBuilder {
program_dir: Utf8PathBuf,
cached_elf_path: Utf8PathBuf,
build_args: BuildArgs,
program_metadata: Metadata,
}
impl ProgramBuilder {
pub fn new(relative_program_dir: impl AsRef<Utf8Path>) -> eyre::Result<Self> {
let host_metadata = MetadataCommand::new()
.no_deps()
.exec()
.context("Host workspace metadata extraction failed")?;
let program_dir = host_metadata.workspace_root.join(relative_program_dir);
let program_dir_meta = fs::metadata(&program_dir)
.with_context(|| format!("Checking the program dir ({program_dir})"))?;
eyre::ensure!(
program_dir_meta.is_dir(),
"Program path is not a directory ({program_dir})",
);
let manifest_dir = env::var("CARGO_MANIFEST_DIR").context("Cannot obtain manifest dir")?;
let cached_elf_path = Utf8Path::new(&manifest_dir).join(super::CACHED_ELF_PATH);
let program_metadata = MetadataCommand::new()
.no_deps()
.current_dir(&program_dir)
.exec()
.context("Program workspace metadata extraction failed")?;
let build_args = BuildArgs {
docker: true,
tag: String::from(super::DEFAULT_DOCKER_TAG),
workspace_directory: Some(host_metadata.workspace_root.to_string()),
..Default::default()
};
Ok(Self {
program_dir,
cached_elf_path,
build_args,
program_metadata,
})
}
pub fn docker_tag(mut self, docker_tag: impl Into<String>) -> Self {
self.build_args.tag = docker_tag.into();
self
}
pub fn cached_elf_path(mut self, cached_elf_path: impl Into<Utf8PathBuf>) -> Self {
self.cached_elf_path = cached_elf_path.into();
self
}
pub fn add_args(mut self, extra_args: impl IntoIterator<Item = String>) -> Self {
clap::Parser::update_from(&mut self.build_args, extra_args);
self
}
pub fn add_rustflags(mut self, extra_args: impl IntoIterator<Item = String>) -> Self {
self.build_args.rustflags.extend(extra_args);
self
}
fn built_elf_path(&self) -> eyre::Result<Utf8PathBuf> {
let args = &self.build_args;
let mut paths_iter = sp1_build::generate_elf_paths(&self.program_metadata, Some(args))
.map_err(|e| eyre!(e))
.context("Failed to extract zkvm ELF paths")?
.into_iter();
let (_package, path) = paths_iter.next().ok_or_eyre("No zkvm ELF paths")?;
eyre::ensure!(paths_iter.next().is_none(), "Too many zkvm ELF paths");
Ok(path)
}
fn copy_elf(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> eyre::Result<()> {
if let Some(cached_elf_dir) = destination.as_ref().parent() {
fs::create_dir_all(cached_elf_dir)
.context("Failed to create directory for zkvm ELF")?;
}
let source_tmp = Path::new(&env::var("OUT_DIR").context("Getting build OUT_DIR")?)
.join(source.as_ref().file_name().unwrap_or("zkvm-elf".as_ref()))
.with_extension(".temporary");
fs::copy(source, source_tmp.as_path()).context("Failed to copy zkvm ELF")?;
fs::rename(source_tmp, destination).context("Failed to move zkvm ELF")?;
Ok(())
}
fn build_program(self) -> eyre::Result<Utf8PathBuf> {
eprintln!("Program dir: {}", self.program_dir.as_str());
let elf_path = self.built_elf_path()?;
sp1_build::build_program_with_args(self.program_dir.as_str(), self.build_args);
println!("cargo::rustc-env=AGGLAYER_ELF_PATH={elf_path}");
Ok(elf_path)
}
fn build_and_refresh(self) -> eyre::Result<Utf8PathBuf> {
let cached_elf_path = self.cached_elf_path.clone();
let elf_path = self.build_program()?;
Self::copy_elf(elf_path.as_path(), cached_elf_path).context("Copying zkvm ELF to cache")?;
Ok(elf_path)
}
fn take_from_cache(self) -> eyre::Result<Utf8PathBuf> {
let cached_elf_path = self.cached_elf_path;
println!("cargo::rerun-if-changed={cached_elf_path}");
println!("cargo::rustc-env=AGGLAYER_ELF_PATH={cached_elf_path}");
Ok(cached_elf_path)
}
pub fn run_mode(self, mode: Mode) -> eyre::Result<Utf8PathBuf> {
match mode {
Mode::Build => self.build_program(),
Mode::Refresh => self.build_and_refresh(),
Mode::Cached => self.take_from_cache(),
}
}
pub fn run(self) -> eyre::Result<Utf8PathBuf> {
self.run_mode(Mode::from_env()?)
}
}