From 98b459b925d46ce1df23affe3dc3b5a2a00a99ae Mon Sep 17 00:00:00 2001 From: Levi Cook Date: Tue, 10 Jun 2025 20:27:19 -0600 Subject: [PATCH 1/2] fix(build): solve cargo bench deadlock and refactor build scripts - Eliminate file lock contention by using isolated temp directories - Extract shared build logic to prevent duplication (~200 lines) - Add aggressive error handling with clear failure diagnostics - Use CARGO_TARGET_DIR isolation instead of shared target/ directory - Fix .so file paths to use correct sbf-solana-solana/release location - Add cu_bench feature scaffolding for compute unit benchmarking - Consolidate build_anchor_program and build_pinocchio_program logic This resolves indefinite hanging in `cargo bench` caused by flock contention between main cargo process and build script subprocesses. The refactored build system is more robust, maintainable, and enables reliable benchmarking for compute unit measurement workflows. --- .vscode/settings.json | 7 +- crates/litesvm-testing/Cargo.toml | 9 ++ .../benches/cu_bench_sol_transfer.rs | 7 + .../benches/cu_bench_spl_transfer.rs | 7 + .../src/anchor_testing/build.rs | 50 +++---- crates/litesvm-testing/src/cu_bench/mod.rs | 33 +++++ crates/litesvm-testing/src/lib.rs | 138 ++++++++++++++++++ .../src/pinocchio_testing/build.rs | 40 +---- .../anchor/simple-anchor-tests/src/lib.rs | 6 +- .../simple-pinocchio-tests/src/lib.rs | 4 +- 10 files changed, 228 insertions(+), 73 deletions(-) create mode 100644 crates/litesvm-testing/benches/cu_bench_sol_transfer.rs create mode 100644 crates/litesvm-testing/benches/cu_bench_spl_transfer.rs create mode 100644 crates/litesvm-testing/src/cu_bench/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index cc61601..c77d568 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { - "rust-analyzer.cargo.features": ["anchor", "bpf-entrypoint", "pinocchio"], + "rust-analyzer.cargo.features": [ + "anchor", + "bpf-entrypoint", + "cu_bench", + "pinocchio", + ], "rust-analyzer.cargo.targetDir": "target/vscode" } diff --git a/crates/litesvm-testing/Cargo.toml b/crates/litesvm-testing/Cargo.toml index 9055298..7025b25 100644 --- a/crates/litesvm-testing/Cargo.toml +++ b/crates/litesvm-testing/Cargo.toml @@ -25,8 +25,17 @@ path = "src/lib.rs" [features] anchor = [] +cu_bench = [] pinocchio = [] +# [[bench]] +# name = "cu_measurements_sol_transfer" +# harness = false + +# [[bench]] +# name = "cu_measurements_spl_token" +# harness = false + [dependencies] litesvm = { workspace = true } num-traits = { workspace = true } diff --git a/crates/litesvm-testing/benches/cu_bench_sol_transfer.rs b/crates/litesvm-testing/benches/cu_bench_sol_transfer.rs new file mode 100644 index 0000000..bc1c0c2 --- /dev/null +++ b/crates/litesvm-testing/benches/cu_bench_sol_transfer.rs @@ -0,0 +1,7 @@ +use std::time::Duration; + +fn main() { + println!("Running SOL transfer CU measurements..."); + std::thread::sleep(Duration::from_millis(100)); // Simulate work + println!("Done! (placeholder)"); +} diff --git a/crates/litesvm-testing/benches/cu_bench_spl_transfer.rs b/crates/litesvm-testing/benches/cu_bench_spl_transfer.rs new file mode 100644 index 0000000..59fa7ae --- /dev/null +++ b/crates/litesvm-testing/benches/cu_bench_spl_transfer.rs @@ -0,0 +1,7 @@ +use std::time::Duration; + +fn main() { + println!("Running SPL transfer CU measurements..."); + std::thread::sleep(Duration::from_millis(100)); // Simulate work + println!("Done! (placeholder)"); +} diff --git a/crates/litesvm-testing/src/anchor_testing/build.rs b/crates/litesvm-testing/src/anchor_testing/build.rs index ae4aea3..23d6148 100644 --- a/crates/litesvm-testing/src/anchor_testing/build.rs +++ b/crates/litesvm-testing/src/anchor_testing/build.rs @@ -1,41 +1,29 @@ -use std::{path::Path, process::Command}; +use std::path::Path; /// Build an anchor program from a given path. /// +/// This is the standard entry point for compiling Anchor programs in test build scripts. +/// It uses default build settings without additional features. +/// /// # Arguments /// /// * `program_path` - The path to the anchor program. (contains Anchor.toml, Cargo.toml and src/ directory) /// +/// For custom feature configurations, use [`build_anchor_program_with_features`]. pub fn build_anchor_program>(program_path: P) { - let program_manifest = program_path.as_ref().join("Cargo.toml"); - let program_src = program_path.as_ref().join("src"); - - // Tell cargo to rerun this build script if the program source changes - println!("cargo:rerun-if-changed={}", program_manifest.display()); - println!("cargo:rerun-if-changed={}", program_src.display()); - - // Build the anchor program - let output = Command::new("cargo") - .args([ - "build-sbf", - "--manifest-path", - &program_manifest.to_string_lossy(), - ]) - .output(); + build_anchor_program_with_features(program_path, &[]); +} - match output { - Ok(output) => { - if !output.status.success() { - eprintln!("Failed to build anchor program:"); - eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - std::process::exit(1); - } - } - Err(e) => { - eprintln!("Failed to execute cargo build-sbf: {}", e); - eprintln!("Make sure you have the solana CLI tools installed and in your PATH"); - std::process::exit(1); - } - } +/// Build an anchor program from a given path with specific features. +/// +/// This function provides fine-grained control over which features are enabled during +/// Anchor program compilation. +/// +/// # Arguments +/// +/// * `program_path` - The path to the anchor program. (contains Anchor.toml, Cargo.toml and src/ directory) +/// * `features` - Array of feature names to enable during compilation +/// +pub fn build_anchor_program_with_features>(program_path: P, features: &[&str]) { + crate::build_solana_program_internal(program_path, features); } diff --git a/crates/litesvm-testing/src/cu_bench/mod.rs b/crates/litesvm-testing/src/cu_bench/mod.rs new file mode 100644 index 0000000..2c63462 --- /dev/null +++ b/crates/litesvm-testing/src/cu_bench/mod.rs @@ -0,0 +1,33 @@ +// // Core trait +// trait CuBenchInstruction { ... } + +// // Runner +// struct CuBenchRunner { ... } + +// // Database/estimates +// struct CuBenchDatabase { ... } +// struct CuBenchEstimate { ... } + +// // TX builder integration +// let estimates = CuBenchDatabase::load(); +// let tx_builder = TxBuilder::new() +// .with_cubench_estimates(estimates); + +// // benches/cu_measurements_sol_transfer.rs +// use litesvm_testing::*; + +// #[derive(Clone, Debug, Serialize, Deserialize)] +// pub struct SolTransfer { +// pub amount: u64, +// pub from_balance: u64, +// } + +// impl BenchmarkableInstruction for SolTransfer { +// // trait implementation +// } + +// fn main() { +// let mut runner = BenchmarkRunner::new(); +// let results = runner.benchmark_instruction::(); +// results.write_reports("sol_transfer").unwrap(); +// } diff --git a/crates/litesvm-testing/src/lib.rs b/crates/litesvm-testing/src/lib.rs index 0eda5e4..4f8d8d5 100644 --- a/crates/litesvm-testing/src/lib.rs +++ b/crates/litesvm-testing/src/lib.rs @@ -52,6 +52,9 @@ #[cfg(feature = "anchor")] pub mod anchor_testing; +#[cfg(feature = "cu_bench")] +pub mod cu_bench; + #[cfg(feature = "pinocchio")] pub mod pinocchio_testing; @@ -573,3 +576,138 @@ pub fn setup_svm_and_fee_payer() -> (LiteSVM, Keypair) { (svm, fee_payer) } + +/// Private helper function for building Solana programs with isolated temp directories. +/// +/// This function handles the common logic for both Anchor and Pinocchio program builds: +/// - Sets up isolated temp directory to prevent file lock contention +/// - Runs `cargo build-sbf` with specified features +/// - Extracts workspace root from OUT_DIR environment variable +/// - Copies all built .so files to workspace target directory +/// - Provides aggressive error handling with clear diagnostics +fn build_solana_program_internal>(program_path: P, features: &[&str]) { + use std::{fs, process::Command}; + + let program_manifest = program_path.as_ref().join("Cargo.toml"); + let program_src = program_path.as_ref().join("src"); + + // Tell cargo to rerun this build script if the program source changes + println!("cargo:rerun-if-changed={}", program_manifest.display()); + println!("cargo:rerun-if-changed={}", program_src.display()); + + // Extract program name from Cargo.toml path + let program_name = program_path + .as_ref() + .file_name() + .and_then(|n| n.to_str()) + .expect("Failed to extract program name from path"); + + // Determine target directory - use existing CARGO_TARGET_DIR or create temp + let base_target_dir = std::env::var("CARGO_TARGET_DIR") + .map(|p| std::path::PathBuf::from(p)) + .unwrap_or_else(|_| std::env::temp_dir().join("litesvm-builds")); + + let temp_dir = base_target_dir.join(format!("program-{}", program_name)); + + if let Err(e) = fs::create_dir_all(&temp_dir) { + eprintln!("Failed to create build directory: {}", e); + std::process::exit(1); + } + + // Build the program in isolated directory + let output = Command::new("cargo") + .args([ + "build-sbf", + "--manifest-path", + &program_manifest.to_string_lossy(), + "--features", + &features.join(","), + ]) + .env("CARGO_TARGET_DIR", &temp_dir) + .output(); + + match output { + Ok(output) => { + if !output.status.success() { + eprintln!("Failed to build program:"); + eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + std::process::exit(1); + } + } + Err(e) => { + eprintln!("Failed to execute cargo build-sbf: {}", e); + eprintln!("Make sure you have the solana CLI tools installed and in your PATH"); + std::process::exit(1); + } + } + + // Copy all built .so files to the workspace target directory + let temp_so_dir = temp_dir.join("sbf-solana-solana/release"); + + // Use OUT_DIR to find workspace target directory + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build scripts"); + + // OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out + // Extract workspace root and construct target path + let target_pos = out_dir.find("/target/").unwrap_or_else(|| { + eprintln!("FATAL: Could not find '/target/' in OUT_DIR: {}", out_dir); + eprintln!("Expected OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out"); + eprintln!("This indicates a problem with the cargo build environment."); + std::process::exit(1); + }); + + let workspace_root = &out_dir[..target_pos]; + let workspace_target = std::path::PathBuf::from(format!( + "{}/target/sbf-solana-solana/release", + workspace_root + )); + + if let Err(e) = fs::create_dir_all(&workspace_target) { + eprintln!("Failed to create workspace target directory: {}", e); + std::process::exit(1); + } + + // Find and copy all .so files + let entries = fs::read_dir(&temp_so_dir).unwrap_or_else(|e| { + eprintln!( + "FATAL: Could not read temp build directory: {}", + temp_so_dir.display() + ); + eprintln!("Error: {}", e); + eprintln!("This suggests the build failed or produced no output."); + std::process::exit(1); + }); + + let mut copied_files = 0; + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "so") { + let filename = path.file_name().expect("File should have a name"); + let target_path = workspace_target.join(filename); + + if let Err(e) = fs::copy(&path, &target_path) { + eprintln!( + "FATAL: Failed to copy .so file from {} to {}: {}", + path.display(), + target_path.display(), + e + ); + std::process::exit(1); + } + + println!("Successfully built and copied: {}", target_path.display()); + copied_files += 1; + } + } + + if copied_files == 0 { + eprintln!( + "FATAL: No .so files found in build output directory: {}", + temp_so_dir.display() + ); + eprintln!("The program compilation succeeded but produced no deployable artifacts."); + eprintln!("Check that the program builds correctly with 'cargo build-sbf'."); + std::process::exit(1); + } +} diff --git a/crates/litesvm-testing/src/pinocchio_testing/build.rs b/crates/litesvm-testing/src/pinocchio_testing/build.rs index b06062e..9d35e2c 100644 --- a/crates/litesvm-testing/src/pinocchio_testing/build.rs +++ b/crates/litesvm-testing/src/pinocchio_testing/build.rs @@ -36,7 +36,7 @@ //! build_pinocchio_program("../my-pinocchio-program"); //! ``` -use std::{path::Path, process::Command}; +use std::path::Path; /// Build a Pinocchio program from a given path with the default features. /// @@ -79,8 +79,8 @@ pub fn build_pinocchio_program>(program_path: P) { /// # Build process /// /// 1. **Change detection**: Registers the Cargo.toml and src/ directory for rebuild triggers -/// 2. **Compilation**: Runs `cargo build-sbf` with specified features -/// 3. **Output**: Places compiled `.so` file in `target/deploy/` directory +/// 2. **Compilation**: Runs `cargo build-sbf` with specified features in a temp directory +/// 3. **Output**: Copies compiled `.so` file to `target/sbf-solana-solana/release/` directory /// 4. **Error handling**: Provides detailed error messages for build failures /// /// # Example @@ -111,37 +111,5 @@ pub fn build_pinocchio_program>(program_path: P) { /// sh -c "$(curl -sSfL https://release.solana.com/stable/install)" /// ``` pub fn build_pinocchio_program_with_features>(program_path: P, features: &[&str]) { - let program_manifest = program_path.as_ref().join("Cargo.toml"); - let program_src = program_path.as_ref().join("src"); - - // Tell cargo to rerun this build script if the program source changes - println!("cargo:rerun-if-changed={}", program_manifest.display()); - println!("cargo:rerun-if-changed={}", program_src.display()); - - // Build the pinocchio program - let output = Command::new("cargo") - .args([ - "build-sbf", - "--manifest-path", - &program_manifest.to_string_lossy(), - "--features", - &features.join(","), - ]) - .output(); - - match output { - Ok(output) => { - if !output.status.success() { - eprintln!("Failed to build pinocchio program:"); - eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - std::process::exit(1); - } - } - Err(e) => { - eprintln!("Failed to execute cargo build-sbf: {}", e); - eprintln!("Make sure you have the solana CLI tools installed and in your PATH"); - std::process::exit(1); - } - } + crate::build_solana_program_internal(program_path, features); } diff --git a/examples/anchor/simple-anchor-tests/src/lib.rs b/examples/anchor/simple-anchor-tests/src/lib.rs index 7117a58..a5c63f2 100644 --- a/examples/anchor/simple-anchor-tests/src/lib.rs +++ b/examples/anchor/simple-anchor-tests/src/lib.rs @@ -35,7 +35,7 @@ /// This works in conjunction with: /// - `build.rs` - Triggers program compilation via `litesvm_testing::anchor_testing::build_anchor_program` /// - `simple_anchor_program::ID` - The program's declared public key -/// - `target/deploy/` - Solana's standard output location for compiled programs +/// - `target/sbf-solana-solana/release/` - Solana's standard output location for compiled programs /// /// For more complex scenarios, see `litesvm_testing::anchor_testing` for the underlying build utilities. pub fn load_simple_anchor_program(svm: &mut litesvm::LiteSVM) { @@ -43,7 +43,7 @@ pub fn load_simple_anchor_program(svm: &mut litesvm::LiteSVM) { simple_anchor_program::ID, include_bytes!(concat!( std::env!("CARGO_MANIFEST_DIR"), - "/../../../target/deploy/simple_anchor_program.so" - )), + "/../../../target/sbf-solana-solana/release/simple_anchor_program.so" + ),), ); } diff --git a/examples/pinocchio/simple-pinocchio-tests/src/lib.rs b/examples/pinocchio/simple-pinocchio-tests/src/lib.rs index 657abd9..69f341a 100644 --- a/examples/pinocchio/simple-pinocchio-tests/src/lib.rs +++ b/examples/pinocchio/simple-pinocchio-tests/src/lib.rs @@ -50,7 +50,7 @@ /// This works in conjunction with: /// - `build.rs` - Triggers program compilation via `litesvm_testing::pinocchio_testing::build_pinocchio_program` /// - `simple_pinocchio_program::ID` - The program's declared public key -/// - `target/deploy/` - Solana's standard output location for compiled programs +/// - `target/sbf-solana-solana/release/` - Solana's standard output location for compiled programs /// - `bpf-entrypoint` feature - Required for Pinocchio BPF compilation /// /// For more complex scenarios or custom features, see `litesvm_testing::pinocchio_testing` for the underlying build utilities. @@ -59,7 +59,7 @@ pub fn load_simple_pinocchio_program(svm: &mut litesvm::LiteSVM) { simple_pinocchio_program::ID.into(), include_bytes!(concat!( std::env!("CARGO_MANIFEST_DIR"), - "/../../../target/deploy/simple_pinocchio_program.so" + "/../../../target/sbf-solana-solana/release/simple_pinocchio_program.so" )), ); } From d0bd401171a715ac629c412bcb77ddb008ef4385 Mon Sep 17 00:00:00 2001 From: Levi Cook Date: Tue, 10 Jun 2025 20:55:38 -0600 Subject: [PATCH 2/2] refactor: extract shared build logic into dedicated module - Move build_solana_program_internal from lib.rs to build_internal.rs - Add cargo clean step before building to ensure fresh artifacts - Update function calls in anchor and pinocchio modules to use new path - Gate build_internal module with appropriate feature flags - Remove redundant documentation reference in anchor build function - Improve file extension checking with is_some_and for clarity This refactoring improves code organization by separating build utilities from the main library interface while enhancing build reliability. --- .../src/anchor_testing/build.rs | 3 +- crates/litesvm-testing/src/build_internal.rs | 165 ++++++++++++++++++ crates/litesvm-testing/src/lib.rs | 138 +-------------- .../src/pinocchio_testing/build.rs | 2 +- 4 files changed, 170 insertions(+), 138 deletions(-) create mode 100644 crates/litesvm-testing/src/build_internal.rs diff --git a/crates/litesvm-testing/src/anchor_testing/build.rs b/crates/litesvm-testing/src/anchor_testing/build.rs index 23d6148..5c82608 100644 --- a/crates/litesvm-testing/src/anchor_testing/build.rs +++ b/crates/litesvm-testing/src/anchor_testing/build.rs @@ -9,7 +9,6 @@ use std::path::Path; /// /// * `program_path` - The path to the anchor program. (contains Anchor.toml, Cargo.toml and src/ directory) /// -/// For custom feature configurations, use [`build_anchor_program_with_features`]. pub fn build_anchor_program>(program_path: P) { build_anchor_program_with_features(program_path, &[]); } @@ -25,5 +24,5 @@ pub fn build_anchor_program>(program_path: P) { /// * `features` - Array of feature names to enable during compilation /// pub fn build_anchor_program_with_features>(program_path: P, features: &[&str]) { - crate::build_solana_program_internal(program_path, features); + crate::build_internal::build_solana_program_internal(program_path, features); } diff --git a/crates/litesvm-testing/src/build_internal.rs b/crates/litesvm-testing/src/build_internal.rs new file mode 100644 index 0000000..202af9a --- /dev/null +++ b/crates/litesvm-testing/src/build_internal.rs @@ -0,0 +1,165 @@ +/// Private helper function for building Solana programs with isolated temp directories. +/// +/// This function handles the common logic for both Anchor and Pinocchio program builds: +/// - Sets up isolated temp directory to prevent file lock contention +/// - Cleans any existing artifacts to ensure fresh builds +/// - Runs `cargo build-sbf` with specified features +/// - Extracts workspace root from OUT_DIR environment variable +/// - Copies all built .so files to workspace target directory +/// - Provides aggressive error handling with clear diagnostics +pub(crate) fn build_solana_program_internal>( + program_path: P, + features: &[&str], +) { + use std::{fs, process::Command}; + + let program_manifest = program_path.as_ref().join("Cargo.toml"); + let program_src = program_path.as_ref().join("src"); + + // Tell cargo to rerun this build script if the program source changes + println!("cargo:rerun-if-changed={}", program_manifest.display()); + println!("cargo:rerun-if-changed={}", program_src.display()); + + // Extract program name from Cargo.toml path + let program_name = program_path + .as_ref() + .file_name() + .and_then(|n| n.to_str()) + .expect("Failed to extract program name from path"); + + // Determine target directory - use existing CARGO_TARGET_DIR or create temp + let base_target_dir = std::env::var("CARGO_TARGET_DIR") + .map(std::path::PathBuf::from) + .unwrap_or_else(|_| std::env::temp_dir().join("litesvm-builds")); + + let temp_dir = base_target_dir.join(format!("program-{}", program_name)); + + if let Err(e) = fs::create_dir_all(&temp_dir) { + eprintln!("Failed to create build directory: {}", e); + std::process::exit(1); + } + + // Build the program in isolated directory + // First clean to ensure no stale artifacts + let clean_output = Command::new("cargo") + .args([ + "clean", + "--manifest-path", + &program_manifest.to_string_lossy(), + ]) + .env("CARGO_TARGET_DIR", &temp_dir) + .output(); + + match clean_output { + Ok(output) => { + if !output.status.success() { + eprintln!("Failed to clean program:"); + eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + std::process::exit(1); + } + } + Err(e) => { + eprintln!("Failed to execute cargo clean: {}", e); + eprintln!("Make sure you have cargo installed and in your PATH"); + std::process::exit(1); + } + } + + // Now build the program + let output = Command::new("cargo") + .args([ + "build-sbf", + "--manifest-path", + &program_manifest.to_string_lossy(), + "--features", + &features.join(","), + ]) + .env("CARGO_TARGET_DIR", &temp_dir) + .output(); + + match output { + Ok(output) => { + if !output.status.success() { + eprintln!("Failed to build program:"); + eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + std::process::exit(1); + } + } + Err(e) => { + eprintln!("Failed to execute cargo build-sbf: {}", e); + eprintln!("Make sure you have the solana CLI tools installed and in your PATH"); + std::process::exit(1); + } + } + + // Copy all built .so files to the workspace target directory + let temp_so_dir = temp_dir.join("sbf-solana-solana/release"); + + // Use OUT_DIR to find workspace target directory + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build scripts"); + + // OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out + // Extract workspace root and construct target path + let target_pos = out_dir.find("/target/").unwrap_or_else(|| { + eprintln!("FATAL: Could not find '/target/' in OUT_DIR: {}", out_dir); + eprintln!("Expected OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out"); + eprintln!("This indicates a problem with the cargo build environment."); + std::process::exit(1); + }); + + let workspace_root = &out_dir[..target_pos]; + let workspace_target = std::path::PathBuf::from(format!( + "{}/target/sbf-solana-solana/release", + workspace_root + )); + + if let Err(e) = fs::create_dir_all(&workspace_target) { + eprintln!("Failed to create workspace target directory: {}", e); + std::process::exit(1); + } + + // Find and copy all .so files + let entries = fs::read_dir(&temp_so_dir).unwrap_or_else(|e| { + eprintln!( + "FATAL: Could not read temp build directory: {}", + temp_so_dir.display() + ); + eprintln!("Error: {}", e); + eprintln!("This suggests the build failed or produced no output."); + std::process::exit(1); + }); + + let mut copied_files = 0; + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "so") { + let filename = path.file_name().expect("File should have a name"); + let target_path = workspace_target.join(filename); + + if let Err(e) = fs::copy(&path, &target_path) { + eprintln!( + "FATAL: Failed to copy .so file from {} to {}: {}", + path.display(), + target_path.display(), + e + ); + std::process::exit(1); + } + + println!("Successfully built and copied: {}", target_path.display()); + copied_files += 1; + } + } + + if copied_files == 0 { + eprintln!( + "FATAL: No .so files found in build output directory: {}", + temp_so_dir.display() + ); + eprintln!("The program compilation succeeded but produced no deployable artifacts."); + eprintln!("Check that the program builds correctly with 'cargo build-sbf'."); + std::process::exit(1); + } +} diff --git a/crates/litesvm-testing/src/lib.rs b/crates/litesvm-testing/src/lib.rs index 4f8d8d5..2cba335 100644 --- a/crates/litesvm-testing/src/lib.rs +++ b/crates/litesvm-testing/src/lib.rs @@ -52,6 +52,9 @@ #[cfg(feature = "anchor")] pub mod anchor_testing; +#[cfg(any(feature = "anchor", feature = "pinocchio"))] +mod build_internal; + #[cfg(feature = "cu_bench")] pub mod cu_bench; @@ -576,138 +579,3 @@ pub fn setup_svm_and_fee_payer() -> (LiteSVM, Keypair) { (svm, fee_payer) } - -/// Private helper function for building Solana programs with isolated temp directories. -/// -/// This function handles the common logic for both Anchor and Pinocchio program builds: -/// - Sets up isolated temp directory to prevent file lock contention -/// - Runs `cargo build-sbf` with specified features -/// - Extracts workspace root from OUT_DIR environment variable -/// - Copies all built .so files to workspace target directory -/// - Provides aggressive error handling with clear diagnostics -fn build_solana_program_internal>(program_path: P, features: &[&str]) { - use std::{fs, process::Command}; - - let program_manifest = program_path.as_ref().join("Cargo.toml"); - let program_src = program_path.as_ref().join("src"); - - // Tell cargo to rerun this build script if the program source changes - println!("cargo:rerun-if-changed={}", program_manifest.display()); - println!("cargo:rerun-if-changed={}", program_src.display()); - - // Extract program name from Cargo.toml path - let program_name = program_path - .as_ref() - .file_name() - .and_then(|n| n.to_str()) - .expect("Failed to extract program name from path"); - - // Determine target directory - use existing CARGO_TARGET_DIR or create temp - let base_target_dir = std::env::var("CARGO_TARGET_DIR") - .map(|p| std::path::PathBuf::from(p)) - .unwrap_or_else(|_| std::env::temp_dir().join("litesvm-builds")); - - let temp_dir = base_target_dir.join(format!("program-{}", program_name)); - - if let Err(e) = fs::create_dir_all(&temp_dir) { - eprintln!("Failed to create build directory: {}", e); - std::process::exit(1); - } - - // Build the program in isolated directory - let output = Command::new("cargo") - .args([ - "build-sbf", - "--manifest-path", - &program_manifest.to_string_lossy(), - "--features", - &features.join(","), - ]) - .env("CARGO_TARGET_DIR", &temp_dir) - .output(); - - match output { - Ok(output) => { - if !output.status.success() { - eprintln!("Failed to build program:"); - eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - std::process::exit(1); - } - } - Err(e) => { - eprintln!("Failed to execute cargo build-sbf: {}", e); - eprintln!("Make sure you have the solana CLI tools installed and in your PATH"); - std::process::exit(1); - } - } - - // Copy all built .so files to the workspace target directory - let temp_so_dir = temp_dir.join("sbf-solana-solana/release"); - - // Use OUT_DIR to find workspace target directory - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build scripts"); - - // OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out - // Extract workspace root and construct target path - let target_pos = out_dir.find("/target/").unwrap_or_else(|| { - eprintln!("FATAL: Could not find '/target/' in OUT_DIR: {}", out_dir); - eprintln!("Expected OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out"); - eprintln!("This indicates a problem with the cargo build environment."); - std::process::exit(1); - }); - - let workspace_root = &out_dir[..target_pos]; - let workspace_target = std::path::PathBuf::from(format!( - "{}/target/sbf-solana-solana/release", - workspace_root - )); - - if let Err(e) = fs::create_dir_all(&workspace_target) { - eprintln!("Failed to create workspace target directory: {}", e); - std::process::exit(1); - } - - // Find and copy all .so files - let entries = fs::read_dir(&temp_so_dir).unwrap_or_else(|e| { - eprintln!( - "FATAL: Could not read temp build directory: {}", - temp_so_dir.display() - ); - eprintln!("Error: {}", e); - eprintln!("This suggests the build failed or produced no output."); - std::process::exit(1); - }); - - let mut copied_files = 0; - for entry in entries.flatten() { - let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "so") { - let filename = path.file_name().expect("File should have a name"); - let target_path = workspace_target.join(filename); - - if let Err(e) = fs::copy(&path, &target_path) { - eprintln!( - "FATAL: Failed to copy .so file from {} to {}: {}", - path.display(), - target_path.display(), - e - ); - std::process::exit(1); - } - - println!("Successfully built and copied: {}", target_path.display()); - copied_files += 1; - } - } - - if copied_files == 0 { - eprintln!( - "FATAL: No .so files found in build output directory: {}", - temp_so_dir.display() - ); - eprintln!("The program compilation succeeded but produced no deployable artifacts."); - eprintln!("Check that the program builds correctly with 'cargo build-sbf'."); - std::process::exit(1); - } -} diff --git a/crates/litesvm-testing/src/pinocchio_testing/build.rs b/crates/litesvm-testing/src/pinocchio_testing/build.rs index 9d35e2c..8f64c31 100644 --- a/crates/litesvm-testing/src/pinocchio_testing/build.rs +++ b/crates/litesvm-testing/src/pinocchio_testing/build.rs @@ -111,5 +111,5 @@ pub fn build_pinocchio_program>(program_path: P) { /// sh -c "$(curl -sSfL https://release.solana.com/stable/install)" /// ``` pub fn build_pinocchio_program_with_features>(program_path: P, features: &[&str]) { - crate::build_solana_program_internal(program_path, features); + crate::build_internal::build_solana_program_internal(program_path, features); }