sp1_build/lib.rs
1mod build;
2mod command;
3mod utils;
4
5use std::env;
6use std::path::{Path, PathBuf};
7
8use build::build_program_internal;
9pub use build::{execute_build_program, generate_elf_paths};
10pub use command::TOOLCHAIN_NAME;
11
12pub use sp1_primitives::types::Elf;
13
14use clap::{Parser, ValueEnum};
15
16const DEFAULT_DOCKER_TAG: &str = concat!("v", env!("CARGO_PKG_VERSION"));
17pub const DEFAULT_TARGET: &str = "riscv64im-succinct-zkvm-elf";
18const HELPER_TARGET_SUBDIR: &str = "elf-compilation";
19
20/// Clang/clang++ command-line flags for compiling C/C++ for SP1's
21/// `riscv64im-succinct-zkvm-elf` target.
22///
23/// Useful for build scripts that want to bring C code into an SP1 guest
24/// (either as a pure-C guest linked against a libc-style shim, or as
25/// FFI inside a Rust guest). Pair with [`find_lld`] to drive a clang +
26/// ld.lld pipeline by hand, or use [`build_program_staticlib`] +
27/// [`build_program`] for the canonical Rust-staticlib path.
28pub const CLANG_FLAGS: &[&str] = &[
29 "--target=riscv64-unknown-none-elf",
30 "-march=rv64im",
31 "-mabi=lp64",
32 "-ffreestanding",
33 "-fno-builtin",
34 "-fno-stack-protector",
35 "-nostdlibinc",
36];
37
38/// Locate `ld.lld`, preferring a system `PATH` install and falling
39/// back to the bundled copy in any installed SP1 toolchain
40/// (`~/.sp1/toolchains/*/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld.lld`).
41///
42/// Useful for build scripts that need to link C objects against an
43/// SP1 staticlib without requiring a system-wide `lld` install.
44pub fn find_lld() -> Option<PathBuf> {
45 use std::process::Command;
46 if Command::new("ld.lld").arg("--version").output().is_ok_and(|o| o.status.success()) {
47 return Some(PathBuf::from("ld.lld"));
48 }
49 let home = std::env::var_os("HOME")?;
50 let toolchains = Path::new(&home).join(".sp1/toolchains");
51 for entry in std::fs::read_dir(&toolchains).ok()?.flatten() {
52 let candidate = entry.path().join("lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld.lld");
53 if candidate.exists() {
54 return Some(candidate);
55 }
56 }
57 None
58}
59
60/// Build a `crate-type = ["staticlib"]` crate for SP1 via
61/// [`build_program`] and return the path to the resulting `.a`.
62///
63/// `build_program` is bin-oriented and surfaces ELFs via `SP1_ELF_*`
64/// env vars; for staticlibs the artifact path follows a fixed
65/// convention under SP1's helper target subdirectory, so this wrapper
66/// just runs the build and assembles the path from cargo metadata.
67///
68/// Path layout: `<crate>/target/elf-compilation/<triple>/release/lib<lib_name>.a`.
69///
70/// Panics if cargo metadata can't be read or the staticlib doesn't
71/// exist after the build.
72pub fn build_program_staticlib(path: &str) -> PathBuf {
73 let manifest = Path::new(path).join("Cargo.toml");
74 let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
75 let metadata = metadata_cmd.manifest_path(&manifest).exec().unwrap_or_else(|e| {
76 panic!("failed to read cargo metadata from {}: {e}", manifest.display())
77 });
78 let root_package = metadata
79 .root_package()
80 .unwrap_or_else(|| panic!("no root package at {}", manifest.display()));
81 let lib_target = root_package
82 .targets
83 .iter()
84 .find(|t| t.kind.iter().any(|k| k == "staticlib"))
85 .unwrap_or_else(|| panic!("crate {} has no `staticlib` target", root_package.name));
86
87 build_program(path);
88
89 let staticlib = metadata
90 .target_directory
91 .join(HELPER_TARGET_SUBDIR)
92 .join(DEFAULT_TARGET)
93 .join("release")
94 .join(format!("lib{}.a", lib_target.name));
95 if !staticlib.as_std_path().exists() {
96 panic!(
97 "expected staticlib at {} after `build_program` — did the build fail silently?",
98 staticlib
99 );
100 }
101 staticlib.into_std_path_buf()
102}
103
104/// Controls the warning message verbosity in the build process.
105#[derive(Clone, Copy, ValueEnum, Debug, Default)]
106pub enum WarningLevel {
107 /// Show all warning messages (default).
108 #[default]
109 All,
110 /// Suppress non-essential warnings; show only critical stuff.
111 Minimal,
112}
113
114/// Compile an SP1 program.
115///
116/// Additional arguments are useful for configuring the build process, including options for using
117/// Docker, specifying binary and ELF names, ignoring Rust version checks, and enabling specific
118/// features.
119#[derive(Clone, Parser, Debug)]
120pub struct BuildArgs {
121 #[arg(
122 long,
123 action,
124 help = "Run compilation using a Docker container for reproducible builds."
125 )]
126 pub docker: bool,
127 #[arg(
128 long,
129 help = "The ghcr.io/succinctlabs/sp1 image tag to use when building with Docker.",
130 default_value = DEFAULT_DOCKER_TAG
131 )]
132 pub tag: String,
133 #[arg(
134 long,
135 action,
136 value_delimiter = ',',
137 help = "Space or comma separated list of features to activate"
138 )]
139 pub features: Vec<String>,
140 #[arg(
141 long,
142 action,
143 value_delimiter = ',',
144 help = "Space or comma separated list of extra flags to invokes `rustc` with"
145 )]
146 pub rustflags: Vec<String>,
147 #[arg(long, action, help = "Do not activate the `default` feature")]
148 pub no_default_features: bool,
149 #[arg(long, action, help = "Ignore `rust-version` specification in packages")]
150 pub ignore_rust_version: bool,
151 #[arg(long, action, help = "Assert that `Cargo.lock` will remain unchanged")]
152 pub locked: bool,
153 #[arg(
154 short,
155 long,
156 action,
157 help = "Build only the specified packages",
158 num_args = 1..
159 )]
160 pub packages: Vec<String>,
161 #[arg(
162 alias = "bin",
163 long,
164 action,
165 help = "Build only the specified binaries",
166 num_args = 1..
167 )]
168 pub binaries: Vec<String>,
169 #[arg(long, action, requires = "output_directory", help = "ELF binary name")]
170 pub elf_name: Option<String>,
171 #[arg(alias = "out-dir", long, action, help = "Copy the compiled ELF to this directory")]
172 pub output_directory: Option<String>,
173
174 #[arg(
175 alias = "workspace-dir",
176 long,
177 action,
178 help = "The top level directory to be used in the docker invocation."
179 )]
180 pub workspace_directory: Option<String>,
181
182 #[arg(long, value_enum, default_value = "all", help = "Control warning message verbosity")]
183 pub warning_level: WarningLevel,
184
185 #[arg(
186 long,
187 action,
188 help = "Disable Docker volume caching for cargo registry and git dependencies."
189 )]
190 pub no_docker_cache: bool,
191}
192
193// Implement default args to match clap defaults.
194impl Default for BuildArgs {
195 #[allow(clippy::uninlined_format_args)]
196 fn default() -> Self {
197 Self {
198 docker: false,
199 tag: DEFAULT_DOCKER_TAG.to_string(),
200 features: vec![],
201 rustflags: vec![],
202 ignore_rust_version: false,
203 packages: vec![],
204 binaries: vec![],
205 elf_name: None,
206 output_directory: None,
207 locked: false,
208 no_default_features: false,
209 workspace_directory: None,
210 warning_level: WarningLevel::All,
211 no_docker_cache: false,
212 }
213 }
214}
215
216/// Builds the program if the program at the specified path, or one of its dependencies, changes.
217///
218/// This function monitors the program and its dependencies for changes. If any changes are
219/// detected, it triggers a rebuild of the program.
220///
221/// # Arguments
222///
223/// * `path` - A string slice that holds the path to the program directory.
224///
225/// This function is useful for automatically rebuilding the program during development
226/// when changes are made to the source code or its dependencies.
227///
228/// Set the `SP1_SKIP_PROGRAM_BUILD` environment variable to `true` to skip building the program.
229pub fn build_program(path: &str) {
230 build_program_internal(path, None)
231}
232
233/// Builds the program with the given arguments if the program at path, or one of its dependencies,
234/// changes.
235///
236/// # Arguments
237///
238/// * `path` - A string slice that holds the path to the program directory.
239/// * `args` - A [`BuildArgs`] struct that contains various build configuration options.
240///
241/// Set the `SP1_SKIP_PROGRAM_BUILD` environment variable to `true` to skip building the program.
242pub fn build_program_with_args(path: &str, args: BuildArgs) {
243 build_program_internal(path, Some(args))
244}
245
246// /// Returns the verification key for the provided program.
247// ///
248// /// # Arguments
249// ///
250// /// * `path` - A string slice that holds the path to the program directory.
251// /// * `target_name` - A string slice that holds the binary target.
252// ///
253// /// Note: If used in a script `build.rs`, this function should be called *after*
254// [`build_program`] /// to returns the vkey corresponding to the latest program version which has
255// just been compiled. pub async fn vkey(path: &str, target_name: &str) -> String {
256// let program_dir = std::path::Path::new(path);
257// let metadata_file = program_dir.join("Cargo.toml");
258// let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
259// let metadata = metadata_cmd.manifest_path(metadata_file).exec().unwrap();
260// let target_elf_paths =
261// generate_elf_paths(&metadata, None).expect("failed to collect target ELF paths");
262// let (_, path) =
263// target_elf_paths.iter().find(|(t, _)| t == target_name).expect("failed to find the
264// target"); let prover = Local
265// let mut file = File::open(path).unwrap();
266// let mut elf = Vec::new();
267
268// file.read_to_end(&mut elf).unwrap();
269// let (_, _, vk) = prover.core().setup(&elf).await;
270// vk.bytes32()
271// }
272
273// /// Returns the verification keys for the provided programs in a [`HashMap`] with the target
274// names /// as keys and vkeys as values.
275// ///
276// /// # Arguments
277// ///
278// /// * `path` - A string slice that holds the path to the program directory.
279// /// * `args` - A [`BuildArgs`] struct that contains various build configuration options.
280// ///
281// /// Note: If used in a script `build.rs`, this function should be called *after*
282// [`build_program`] /// to returns the vkey corresponding to the latest program version which has
283// just been compiled. pub fn vkeys(path: &str, args: BuildArgs) -> HashMap<String, String> {
284// let program_dir = std::path::Path::new(path);
285// let metadata_file = program_dir.join("Cargo.toml");
286// let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
287// let metadata = metadata_cmd.manifest_path(metadata_file).exec().unwrap();
288// let target_elf_paths =
289// generate_elf_paths(&metadata, Some(&args)).expect("failed to collect target ELF paths");
290// let prover = SP1Prover::<CpuProverComponents>::new();
291
292// target_elf_paths
293// .into_iter()
294// .map(|(target_name, elf_path)| {
295// let mut file = File::open(elf_path).unwrap();
296// let mut elf = Vec::new();
297// file.read_to_end(&mut elf).unwrap();
298
299// let (_, _, _, vk) = prover.setup(&elf);
300// let vk = vk.bytes32();
301
302// (target_name, vk)
303// })
304// .collect()
305// }
306
307/// Returns the raw ELF bytes by the zkVM program target name.
308///
309/// Note that this only works when using `sp1_build::build_program` or
310/// `sp1_build::build_program_with_args` in a build script.
311///
312/// By default, the program target name is the same as the program crate name. However, this might
313/// not be the case for non-standard project structures. For example, placing the entrypoint source
314/// file at `src/bin/my_entry.rs` would result in the program target being named `my_entry`, in
315/// which case the invocation should be `include_elf!("my_entry")` instead.
316#[macro_export]
317macro_rules! include_elf {
318 ($arg:tt) => {{
319 // TODO: --all-features forces this branch. feature flags may not be the right solution here
320 $crate::Elf::Static(include_bytes!(env!(concat!("SP1_ELF_", $arg))))
321 }};
322}