1use std::path::PathBuf;
2
3use anyhow::Result;
4use cargo_metadata::camino::Utf8PathBuf;
5
6use crate::{
7 command::{docker::create_docker_command, local::create_local_command, utils::execute_command},
8 utils::{cargo_rerun_if_changed, current_datetime},
9 BuildArgs, WarningLevel, BUILD_TARGET, HELPER_TARGET_SUBDIR,
10};
11
12pub fn execute_build_program(
26 args: &BuildArgs,
27 program_dir: Option<PathBuf>,
28) -> Result<Vec<(String, Utf8PathBuf)>> {
29 let program_dir = program_dir
31 .unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory."));
32 let program_dir: Utf8PathBuf =
33 program_dir.try_into().expect("Failed to convert PathBuf to Utf8PathBuf");
34
35 let program_metadata_file = program_dir.join("Cargo.toml");
37 let mut program_metadata_cmd = cargo_metadata::MetadataCommand::new();
38 let program_metadata = program_metadata_cmd.manifest_path(program_metadata_file).exec()?;
39
40 let cmd = if args.docker {
42 create_docker_command(args, &program_dir, &program_metadata)?
43 } else {
44 create_local_command(args, &program_dir, &program_metadata)
45 };
46
47 let target_elf_paths = generate_elf_paths(&program_metadata, Some(args))?;
48
49 if target_elf_paths.len() > 1 && args.elf_name.is_some() {
50 anyhow::bail!("--elf-name is not supported when --output-directory is used and multiple ELFs are built.");
51 }
52
53 execute_command(cmd, args.docker)?;
54
55 if let Some(output_directory) = &args.output_directory {
56 let output_directory = PathBuf::from(output_directory);
58
59 if output_directory.is_file() {
61 anyhow::bail!("--output-directory is a file.");
62 }
63
64 std::fs::create_dir_all(&output_directory)?;
66
67 for (_, elf_path) in target_elf_paths.iter() {
69 let elf_path = elf_path.to_path_buf();
70 let elf_name = elf_path.file_name().expect("ELF path has a file name");
71 let output_path = output_directory.join(args.elf_name.as_deref().unwrap_or(elf_name));
72
73 std::fs::copy(&elf_path, &output_path)?;
74 }
75 }
76
77 print_elf_paths_cargo_directives(&target_elf_paths);
78
79 Ok(target_elf_paths)
80}
81
82pub(crate) fn build_program_internal(path: &str, args: Option<BuildArgs>) {
84 let program_dir = std::path::Path::new(path);
86 let metadata_file = program_dir.join("Cargo.toml");
87 let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
88 let metadata = metadata_cmd.manifest_path(metadata_file).exec().unwrap();
89 let root_package = metadata.root_package();
90 let root_package_name = root_package.as_ref().map(|p| p.name.as_str()).unwrap_or("Program");
91
92 let skip_program_build = std::env::var("SP1_SKIP_PROGRAM_BUILD")
94 .map(|v| v.eq_ignore_ascii_case("true"))
95 .unwrap_or(false);
96 if skip_program_build {
97 let target_elf_paths = generate_elf_paths(&metadata, args.as_ref())
99 .expect("failed to collect target ELF paths");
100
101 print_elf_paths_cargo_directives(&target_elf_paths);
102
103 println!(
104 "cargo:warning=Build skipped for {} at {} due to SP1_SKIP_PROGRAM_BUILD flag",
105 root_package_name,
106 current_datetime()
107 );
108 return;
109 }
110
111 cargo_rerun_if_changed(&metadata, program_dir);
113
114 let is_clippy_driver = std::env::var("RUSTC_WORKSPACE_WRAPPER")
118 .map(|val| val.contains("clippy-driver"))
119 .unwrap_or(false);
120 if is_clippy_driver {
121 let target_elf_paths = generate_elf_paths(&metadata, args.as_ref())
123 .expect("failed to collect target ELF paths");
124
125 print_elf_paths_cargo_directives(&target_elf_paths);
126
127 println!("cargo:warning=Skipping build due to clippy invocation.");
128 return;
129 }
130
131 let path_output = if let Some(args) = &args {
133 execute_build_program(args, Some(program_dir.to_path_buf()))
134 } else {
135 execute_build_program(&BuildArgs::default(), Some(program_dir.to_path_buf()))
136 };
137 if let Err(err) = path_output {
138 panic!("Failed to build SP1 program: {err}.");
139 }
140
141 if args.map(|args| matches!(args.warning_level, WarningLevel::All)).unwrap_or(true) {
142 println!("cargo:warning={} built at {}", root_package_name, current_datetime());
143 }
144}
145
146pub fn generate_elf_paths(
148 metadata: &cargo_metadata::Metadata,
149 args: Option<&BuildArgs>,
150) -> Result<Vec<(String, Utf8PathBuf)>> {
151 let mut target_elf_paths = vec![];
152 let packages_to_iterate = if let Some(args) = args {
153 if !args.packages.is_empty() {
154 args.packages
155 .iter()
156 .map(|wanted_package| {
157 metadata
158 .packages
159 .iter()
160 .find(|p| p.name == *wanted_package)
161 .ok_or_else(|| {
162 anyhow::anyhow!("cannot find package named {}", wanted_package)
163 })
164 .map(|p| p.id.clone())
165 })
166 .collect::<anyhow::Result<Vec<_>>>()?
167 } else {
168 metadata.workspace_default_members.to_vec()
169 }
170 } else {
171 metadata.workspace_default_members.to_vec()
172 };
173
174 for program_crate in packages_to_iterate {
175 let program = metadata
176 .packages
177 .iter()
178 .find(|p| p.id == program_crate)
179 .ok_or_else(|| anyhow::anyhow!("cannot find package for {}", program_crate))?;
180
181 for bin_target in program.targets.iter().filter(|t| {
182 t.kind.contains(&"bin".to_owned()) && t.crate_types.contains(&"bin".to_owned())
183 }) {
184 if let Some(args) = args {
186 if !args.binaries.is_empty() && !args.binaries.contains(&bin_target.name) {
187 continue;
188 }
189 }
190
191 let elf_path = metadata.target_directory.join(HELPER_TARGET_SUBDIR);
192 let elf_path = match args {
193 Some(args) if args.docker => elf_path.join("docker"),
194 _ => elf_path,
195 };
196 let elf_path = elf_path.join(BUILD_TARGET).join("release").join(&bin_target.name);
197
198 target_elf_paths.push((bin_target.name.to_owned(), elf_path));
199 }
200 }
201
202 Ok(target_elf_paths)
203}
204
205fn print_elf_paths_cargo_directives(target_elf_paths: &[(String, Utf8PathBuf)]) {
207 for (target_name, elf_path) in target_elf_paths.iter() {
208 println!("cargo:rustc-env=SP1_ELF_{target_name}={elf_path}");
209 }
210}