1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
use cargo_metadata::Metadata;
use chrono::Local;
pub use sp1_build::BuildArgs;
use std::{path::Path, process::ExitStatus};
fn current_datetime() -> String {
let now = Local::now();
now.format("%Y-%m-%d %H:%M:%S").to_string()
}
/// Re-run the cargo command if the Cargo.toml or Cargo.lock file changes.
fn cargo_rerun_if_changed(metadata: &Metadata, program_dir: &Path) {
// Tell cargo to rerun the script only if program/{src, bin, build.rs, Cargo.toml} changes
// Ref: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rerun-if-changed
let dirs = vec![
program_dir.join("src"),
program_dir.join("bin"),
program_dir.join("build.rs"),
program_dir.join("Cargo.toml"),
];
for dir in dirs {
if dir.exists() {
println!(
"cargo::rerun-if-changed={}",
dir.canonicalize().unwrap().display()
);
}
}
// Re-run the build script if the workspace root's Cargo.lock changes. If the program is its own
// workspace, this will be the program's Cargo.lock.
println!(
"cargo:rerun-if-changed={}",
metadata.workspace_root.join("Cargo.lock").as_str()
);
// Re-run if any local dependency changes.
for package in &metadata.packages {
for dependency in &package.dependencies {
if let Some(path) = &dependency.path {
println!("cargo:rerun-if-changed={}", path.as_str());
}
}
}
}
/// Executes the `cargo prove build` command in the program directory. If there are any cargo prove
/// build arguments, they are added to the command.
fn execute_build_cmd(
program_dir: &impl AsRef<std::path::Path>,
args: Option<BuildArgs>,
) -> Result<std::process::ExitStatus, std::io::Error> {
// 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 Ok(std::process::ExitStatus::default());
}
// Build the program with the given arguments.
let path_output = if let Some(args) = args {
sp1_build::build_program(&args, Some(program_dir.as_ref().to_path_buf()))
} else {
sp1_build::build_program(
&BuildArgs::default(),
Some(program_dir.as_ref().to_path_buf()),
)
};
if let Err(err) = path_output {
panic!("Failed to build SP1 program: {}.", err);
}
Ok(ExitStatus::default())
}
/// Builds the program if the program at the specified path, or one of its dependencies, changes.
///
/// This function monitors the program and its dependencies for changes. If any changes are detected,
/// it triggers a rebuild of the program.
///
/// # Arguments
///
/// * `path` - A string slice that holds the path to the program directory.
///
/// This function is useful for automatically rebuilding the program during development
/// when changes are made to the source code or its dependencies.
///
/// Set the `SP1_SKIP_PROGRAM_BUILD` environment variable to `true` to skip building the program.
pub fn build_program(path: &str) {
build_program_internal(path, None)
}
/// Builds the program with the given arguments if the program at path, or one of its dependencies,
/// changes.
///
/// # Arguments
///
/// * `path` - A string slice that holds the path to the program directory.
/// * `args` - A [`BuildArgs`] struct that contains various build configuration options.
///
/// Set the `SP1_SKIP_PROGRAM_BUILD` environment variable to `true` to skip building the program.
pub fn build_program_with_args(path: &str, args: BuildArgs) {
build_program_internal(path, Some(args))
}
/// Internal helper function to build the program with or without arguments.
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);
let _ = execute_build_cmd(&program_dir, args);
println!(
"cargo:warning={} built at {}",
root_package_name,
current_datetime()
);
}