use std::path::PathBuf;
use anyhow::Result;
use cargo_metadata::camino::Utf8PathBuf;
use crate::{
command::{docker::create_docker_command, local::create_local_command, utils::execute_command},
utils::{cargo_rerun_if_changed, current_datetime},
BuildArgs, WarningLevel, DEFAULT_TARGET, HELPER_TARGET_SUBDIR,
};
pub fn execute_build_program(
args: &BuildArgs,
program_dir: Option<PathBuf>,
) -> Result<Vec<(String, Utf8PathBuf)>> {
let program_dir = program_dir
.unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory."));
let program_dir: Utf8PathBuf =
program_dir.try_into().expect("Failed to convert PathBuf to Utf8PathBuf");
let program_metadata_file = program_dir.join("Cargo.toml");
let mut program_metadata_cmd = cargo_metadata::MetadataCommand::new();
let program_metadata = program_metadata_cmd.manifest_path(program_metadata_file).exec()?;
let cmd = if args.docker {
create_docker_command(args, &program_dir, &program_metadata)?
} else {
create_local_command(args, &program_dir, &program_metadata)
};
let target_elf_paths = generate_elf_paths(&program_metadata, Some(args))?;
if target_elf_paths.len() > 1 && args.elf_name.is_some() {
anyhow::bail!("--elf-name is not supported when --output-directory is used and multiple ELFs are built.");
}
execute_command(cmd, args.docker)?;
if let Some(output_directory) = &args.output_directory {
let output_directory = PathBuf::from(output_directory);
if output_directory.is_file() {
anyhow::bail!("--output-directory is a file.");
}
std::fs::create_dir_all(&output_directory)?;
for (_, elf_path) in target_elf_paths.iter() {
let elf_path = elf_path.to_path_buf();
let elf_name = elf_path.file_name().expect("ELF path has a file name");
let output_path = output_directory.join(args.elf_name.as_deref().unwrap_or(elf_name));
std::fs::copy(&elf_path, &output_path)?;
}
} else if args.elf_name.is_some() {
println!("cargo:warning=ELF name is set but --output-directory is not used, the ELF will be place in the `target/` directory");
}
print_elf_paths_cargo_directives(&target_elf_paths);
Ok(target_elf_paths)
}
#[allow(clippy::uninlined_format_args)]
pub(crate) fn build_program_internal(path: &str, args: Option<BuildArgs>) {
let program_dir = std::path::Path::new(path);
let metadata_file = program_dir.join("Cargo.toml");
let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
let metadata = metadata_cmd.manifest_path(metadata_file).exec().unwrap();
let root_package = metadata.root_package();
let root_package_name = root_package.as_ref().map(|p| p.name.as_str()).unwrap_or("Program");
let skip_program_build = std::env::var("SP1_SKIP_PROGRAM_BUILD")
.map(|v| v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if skip_program_build {
let target_elf_paths = generate_elf_paths(&metadata, args.as_ref())
.expect("failed to collect target ELF paths");
print_elf_paths_cargo_directives(&target_elf_paths);
println!(
"cargo:warning=Build skipped for {} at {} due to SP1_SKIP_PROGRAM_BUILD flag",
root_package_name,
current_datetime()
);
return;
}
cargo_rerun_if_changed(&metadata, program_dir);
let is_clippy_driver = std::env::var("RUSTC_WORKSPACE_WRAPPER")
.map(|val| val.contains("clippy-driver"))
.unwrap_or(false);
if is_clippy_driver {
let target_elf_paths = generate_elf_paths(&metadata, args.as_ref())
.expect("failed to collect target ELF paths");
print_elf_paths_cargo_directives(&target_elf_paths);
println!("cargo:warning=Skipping build due to clippy invocation.");
return;
}
let path_output = if let Some(args) = &args {
execute_build_program(args, Some(program_dir.to_path_buf()))
} else {
execute_build_program(&BuildArgs::default(), Some(program_dir.to_path_buf()))
};
if let Err(err) = path_output {
panic!("Failed to build SP1 program: {err}.");
}
if args.map(|args| matches!(args.warning_level, WarningLevel::All)).unwrap_or(true) {
println!("cargo:warning={} built at {}", root_package_name, current_datetime());
}
}
pub fn generate_elf_paths(
metadata: &cargo_metadata::Metadata,
args: Option<&BuildArgs>,
) -> Result<Vec<(String, Utf8PathBuf)>> {
let mut target_elf_paths = vec![];
let packages_to_iterate = if let Some(args) = args {
if !args.packages.is_empty() {
args.packages
.iter()
.map(|wanted_package| {
metadata
.packages
.iter()
.find(|p| p.name == *wanted_package)
.ok_or_else(|| {
anyhow::anyhow!("cannot find package named {wanted_package}")
})
.map(|p| p.id.clone())
})
.collect::<anyhow::Result<Vec<_>>>()?
} else {
metadata.workspace_default_members.to_vec()
}
} else {
metadata.workspace_default_members.to_vec()
};
for program_crate in packages_to_iterate {
let program = metadata
.packages
.iter()
.find(|p| p.id == program_crate)
.ok_or_else(|| anyhow::anyhow!("cannot find package for {program_crate}"))?;
for bin_target in program.targets.iter().filter(|t| {
t.kind.contains(&"bin".to_owned()) && t.crate_types.contains(&"bin".to_owned())
}) {
if let Some(args) = args {
if !args.binaries.is_empty() && !args.binaries.contains(&bin_target.name) {
continue;
}
}
let elf_path = metadata.target_directory.join(HELPER_TARGET_SUBDIR);
let elf_path = match args {
Some(args) if args.docker => elf_path.join("docker"),
_ => elf_path,
};
let elf_path = elf_path.join(DEFAULT_TARGET).join("release").join(&bin_target.name);
target_elf_paths.push((bin_target.name.to_owned(), elf_path));
}
}
Ok(target_elf_paths)
}
#[allow(clippy::uninlined_format_args)]
fn print_elf_paths_cargo_directives(target_elf_paths: &[(String, Utf8PathBuf)]) {
for (target_name, elf_path) in target_elf_paths.iter() {
let elf_path_str = elf_path.to_string();
println!("cargo:rustc-env=SP1_ELF_{target_name}={elf_path_str}");
}
}