Skip to content
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
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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"
}
9 changes: 9 additions & 0 deletions crates/litesvm-testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
7 changes: 7 additions & 0 deletions crates/litesvm-testing/benches/cu_bench_sol_transfer.rs
Original file line number Diff line number Diff line change
@@ -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)");
}
7 changes: 7 additions & 0 deletions crates/litesvm-testing/benches/cu_bench_spl_transfer.rs
Original file line number Diff line number Diff line change
@@ -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)");
}
49 changes: 18 additions & 31 deletions crates/litesvm-testing/src/anchor_testing/build.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,28 @@
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)
///
pub fn build_anchor_program<P: AsRef<Path>>(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<P: AsRef<Path>>(program_path: P, features: &[&str]) {
crate::build_internal::build_solana_program_internal(program_path, features);
}
165 changes: 165 additions & 0 deletions crates/litesvm-testing/src/build_internal.rs
Original file line number Diff line number Diff line change
@@ -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<P: AsRef<std::path::Path>>(
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);
}
}
33 changes: 33 additions & 0 deletions crates/litesvm-testing/src/cu_bench/mod.rs
Original file line number Diff line number Diff line change
@@ -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::<SolTransfer>();
// results.write_reports("sol_transfer").unwrap();
// }
6 changes: 6 additions & 0 deletions crates/litesvm-testing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@
#[cfg(feature = "anchor")]
pub mod anchor_testing;

#[cfg(any(feature = "anchor", feature = "pinocchio"))]
mod build_internal;

#[cfg(feature = "cu_bench")]
pub mod cu_bench;

#[cfg(feature = "pinocchio")]
pub mod pinocchio_testing;

Expand Down
40 changes: 4 additions & 36 deletions crates/litesvm-testing/src/pinocchio_testing/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -79,8 +79,8 @@ pub fn build_pinocchio_program<P: AsRef<Path>>(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
Expand Down Expand Up @@ -111,37 +111,5 @@ pub fn build_pinocchio_program<P: AsRef<Path>>(program_path: P) {
/// sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
/// ```
pub fn build_pinocchio_program_with_features<P: AsRef<Path>>(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_internal::build_solana_program_internal(program_path, features);
}
6 changes: 3 additions & 3 deletions examples/anchor/simple-anchor-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@
/// 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) {
svm.add_program(
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"
),),
);
}
Loading