Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: deprecate sp1-helper #1268

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions book/writing-programs/compiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ f9afb8caaef10de9a8aad484c4dd3bfa54ba7218f3fc245a20e8a03ed40b38c617e175328515968a

## Build Script

If you want your program crate to be built automatically whenever you build/run your script crate, you can add a `build.rs` file inside of `script/` (at the same level as `Cargo.toml` of your script crate) that utilizes the `sp1-helper` crate:
If you want your program crate to be built automatically whenever you build/run your script crate, you can add a `build.rs` file inside of `script/` (at the same level as `Cargo.toml` of your script crate) that utilizes the `sp1-build` crate:

```rust,noplayground
{{#include ../../examples/fibonacci/script/build.rs}}
```

The path passed in to `build_program` should point to the directory containing the `Cargo.toml` file for your program. Make sure to also add `sp1-helper` as a build dependency in `script/Cargo.toml`:
The path passed in to `build_program` should point to the directory containing the `Cargo.toml` file for your program. Make sure to also add `sp1-build` as a build dependency in `script/Cargo.toml`:

```toml
[build-dependencies]
sp1-helper = "1.1.0"
sp1-build = "1.2.0"
```

You will see output like the following from the build script if the program has changed, indicating that the program was rebuilt:
Expand All @@ -82,12 +82,12 @@ The above output was generated by running `RUST_LOG=info cargo run --release -vv

### Advanced Build Options

To configure the build process when using the `sp1-helper` crate, you can pass a [`BuildArgs`](https://docs.rs/sp1-helper/1.1.0/sp1_helper/struct.BuildArgs.html) struct to to the [`build_program_with_args`](https://docs.rs/sp1-helper/1.1.0/sp1_helper/fn.build_program_with_args.html) function. The build arguments are the same as the ones available from the `cargo prove build` command.
To configure the build process when using the `sp1-build` crate, you can pass a [`BuildArgs`](https://docs.rs/sp1-build/1.2.0/sp1_build/struct.BuildArgs.html) struct to to the [`build_program_with_args`](https://docs.rs/sp1-build/1.2.0/sp1_build/fn.build_program_with_args.html) function. The build arguments are the same as the ones available from the `cargo prove build` command.

As an example, you could use the following code to build the Fibonacci example with the `docker` flag set to `true` and a custom output directory for the generated ELF:

```rust,noplayground
use sp1_helper::{build_program_with_args, BuildArgs};
use sp1_build::{build_program_with_args, BuildArgs};

fn main() {
let args = BuildArgs {
Expand Down
1 change: 1 addition & 0 deletions crates/build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ cargo_metadata = "0.18.1"
anyhow = { version = "1.0.83" }
clap = { version = "4.5.9", features = ["derive", "env"] }
dirs = "5.0.1"
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
2 changes: 1 addition & 1 deletion crates/build/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# sp1-build
Lightweight crate used to build SP1 programs. Internal crate that is exposed to users via `sp1-cli` and `sp1-helper`.
Lightweight crate used to build SP1 programs. Internal crate that is exposed to users via `sp1-cli`.

Exposes `build_program`, which builds an SP1 program in the local environment or in a docker container with the specified parameters from `BuildArgs`.

Expand Down
101 changes: 101 additions & 0 deletions crates/build/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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, copy_elf_to_output_dir, current_datetime},
BuildArgs,
};

/// Build a program with the specified [`BuildArgs`]. The `program_dir` is specified as an argument
/// when the program is built via `build_program`.
///
/// # Arguments
///
/// * `args` - A reference to a `BuildArgs` struct that holds various arguments used for building
/// the program.
/// * `program_dir` - An optional `PathBuf` specifying the directory of the program to be built.
///
/// # Returns
///
/// * `Result<Utf8PathBuf>` - The path to the built program as a `Utf8PathBuf` on success, or an
/// error on failure.
pub fn execute_build_program(
args: &BuildArgs,
program_dir: Option<PathBuf>,
) -> Result<Utf8PathBuf> {
// If the program directory is not specified, use the current directory.
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");

// Get the program metadata.
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().unwrap();

// Get the command corresponding to Docker or local build.
let cmd = if args.docker {
create_docker_command(args, &program_dir, &program_metadata)?
} else {
create_local_command(args, &program_dir, &program_metadata)
};

execute_command(cmd, args.docker)?;

copy_elf_to_output_dir(args, &program_metadata)
}

/// Internal helper function to build the program with or without arguments.
pub(crate) fn build_program_internal(path: &str, args: Option<BuildArgs>) {
// Get the root package name and metadata.
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");

// Skip the program build if the SP1_SKIP_PROGRAM_BUILD environment variable is set to true.
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 {
println!(
"cargo:warning=Build skipped for {} at {} due to SP1_SKIP_PROGRAM_BUILD flag",
root_package_name,
current_datetime()
);
return;
}

// Activate the build command if the dependencies change.
cargo_rerun_if_changed(&metadata, program_dir);

// Check if RUSTC_WORKSPACE_WRAPPER is set to clippy-driver (i.e. if `cargo clippy` is the
// current compiler). If so, don't execute `cargo prove build` because it breaks
// rust-analyzer's `cargo clippy` feature.
let is_clippy_driver = std::env::var("RUSTC_WORKSPACE_WRAPPER")
.map(|val| val.contains("clippy-driver"))
.unwrap_or(false);
if is_clippy_driver {
println!("cargo:warning=Skipping build due to clippy invocation.");
return;
}

// Build the program with the given arguments.
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);
}

println!("cargo:warning={} built at {}", root_package_name, current_datetime());
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::process::{exit, Command, Stdio};
use anyhow::{Context, Result};
use cargo_metadata::camino::Utf8PathBuf;

use crate::{get_program_build_args, get_rust_compiler_flags, BuildArgs};
use crate::BuildArgs;

use super::utils::{get_program_build_args, get_rust_compiler_flags};

/// Uses SP1_DOCKER_IMAGE environment variable if set, otherwise constructs the image to use based
/// on the provided tag.
Expand All @@ -15,7 +17,7 @@ fn get_docker_image(tag: &str) -> String {
}

/// Creates a Docker command to build the program.
pub fn create_docker_command(
pub(crate) fn create_docker_command(
args: &BuildArgs,
program_dir: &Utf8PathBuf,
program_metadata: &cargo_metadata::Metadata,
Expand Down
47 changes: 47 additions & 0 deletions crates/build/src/command/local.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::{env, process::Command};

use crate::{BuildArgs, HELPER_TARGET_SUBDIR};
use cargo_metadata::camino::Utf8PathBuf;
use dirs::home_dir;

use super::utils::{get_program_build_args, get_rust_compiler_flags};

/// Get the command to build the program locally.
pub(crate) fn create_local_command(
args: &BuildArgs,
program_dir: &Utf8PathBuf,
program_metadata: &cargo_metadata::Metadata,
) -> Command {
let mut command = Command::new("cargo");
let canonicalized_program_dir =
program_dir.canonicalize().expect("Failed to canonicalize program directory");

// If CC_riscv32im_succinct_zkvm_elf is not set, set it to the default C++ toolchain
// downloaded by 'sp1up --c-toolchain'.
if env::var("CC_riscv32im_succinct_zkvm_elf").is_err() {
if let Some(home_dir) = home_dir() {
let cc_path = home_dir.join(".sp1").join("bin").join("riscv32-unknown-elf-gcc");
if cc_path.exists() {
command.env("CC_riscv32im_succinct_zkvm_elf", cc_path);
}
}
}

// When executing the local command:
// 1. Set the target directory to a subdirectory of the program's target directory to avoid
// build
// conflicts with the parent process. Source: https://github.com/rust-lang/cargo/issues/6412
// 2. Set the rustup toolchain to succinct.
// 3. Set the encoded rust flags.
// 4. Remove the rustc configuration, otherwise in a build script it will attempt to compile the
// program with the toolchain of the normal build process, rather than the Succinct
// toolchain.
command
.current_dir(canonicalized_program_dir)
.env("RUSTUP_TOOLCHAIN", "succinct")
.env("CARGO_ENCODED_RUSTFLAGS", get_rust_compiler_flags())
.env_remove("RUSTC")
.env("CARGO_TARGET_DIR", program_metadata.target_directory.join(HELPER_TARGET_SUBDIR))
.args(&get_program_build_args(args));
command
}
3 changes: 3 additions & 0 deletions crates/build/src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub(crate) mod docker;
pub(crate) mod local;
pub(crate) mod utils;
92 changes: 92 additions & 0 deletions crates/build/src/command/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use anyhow::{Context, Result};
use std::{
io::{BufRead, BufReader},
process::{exit, Command, Stdio},
thread,
};

use crate::{BuildArgs, BUILD_TARGET};

/// Get the arguments to build the program with the arguments from the [`BuildArgs`] struct.
pub(crate) fn get_program_build_args(args: &BuildArgs) -> Vec<String> {
let mut build_args = vec![
"build".to_string(),
"--release".to_string(),
"--target".to_string(),
BUILD_TARGET.to_string(),
];

if args.ignore_rust_version {
build_args.push("--ignore-rust-version".to_string());
}

if !args.binary.is_empty() {
build_args.push("--bin".to_string());
build_args.push(args.binary.clone());
}

if !args.features.is_empty() {
build_args.push("--features".to_string());
build_args.push(args.features.join(","));
}

if args.no_default_features {
build_args.push("--no-default-features".to_string());
}

if args.locked {
build_args.push("--locked".to_string());
}

build_args
}

/// Rust flags for compilation of C libraries.
pub(crate) fn get_rust_compiler_flags() -> String {
let rust_flags = [
"-C".to_string(),
"passes=loweratomic".to_string(),
"-C".to_string(),
"link-arg=-Ttext=0x00200800".to_string(),
"-C".to_string(),
"panic=abort".to_string(),
];
rust_flags.join("\x1f")
}

/// Execute the command and handle the output depending on the context.
pub(crate) fn execute_command(mut command: Command, docker: bool) -> Result<()> {
// Add necessary tags for stdout and stderr from the command.
let mut child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("failed to spawn command")?;
let stdout = BufReader::new(child.stdout.take().unwrap());
let stderr = BufReader::new(child.stderr.take().unwrap());

// Add prefix to the output of the process depending on the context.
let msg = match docker {
true => "[sp1] [docker] ",
false => "[sp1] ",
};

// Pipe stdout and stderr to the parent process with [docker] prefix
let stdout_handle = thread::spawn(move || {
stdout.lines().for_each(|line| {
println!("{} {}", msg, line.unwrap());
});
});
stderr.lines().for_each(|line| {
eprintln!("{} {}", msg, line.unwrap());
});
stdout_handle.join().unwrap();

// Wait for the child process to finish and check the result.
let result = child.wait()?;
if !result.success() {
// Error message is already printed by cargo.
exit(result.code().unwrap_or(1))
}
Ok(())
}
Loading
Loading