Skip to content

Commit 98b459b

Browse files
committed
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.
1 parent 1eb858a commit 98b459b

10 files changed

Lines changed: 228 additions & 73 deletions

File tree

.vscode/settings.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
{
2-
"rust-analyzer.cargo.features": ["anchor", "bpf-entrypoint", "pinocchio"],
2+
"rust-analyzer.cargo.features": [
3+
"anchor",
4+
"bpf-entrypoint",
5+
"cu_bench",
6+
"pinocchio",
7+
],
38
"rust-analyzer.cargo.targetDir": "target/vscode"
49
}

crates/litesvm-testing/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,17 @@ path = "src/lib.rs"
2525

2626
[features]
2727
anchor = []
28+
cu_bench = []
2829
pinocchio = []
2930

31+
# [[bench]]
32+
# name = "cu_measurements_sol_transfer"
33+
# harness = false
34+
35+
# [[bench]]
36+
# name = "cu_measurements_spl_token"
37+
# harness = false
38+
3039
[dependencies]
3140
litesvm = { workspace = true }
3241
num-traits = { workspace = true }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::time::Duration;
2+
3+
fn main() {
4+
println!("Running SOL transfer CU measurements...");
5+
std::thread::sleep(Duration::from_millis(100)); // Simulate work
6+
println!("Done! (placeholder)");
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::time::Duration;
2+
3+
fn main() {
4+
println!("Running SPL transfer CU measurements...");
5+
std::thread::sleep(Duration::from_millis(100)); // Simulate work
6+
println!("Done! (placeholder)");
7+
}
Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,29 @@
1-
use std::{path::Path, process::Command};
1+
use std::path::Path;
22

33
/// Build an anchor program from a given path.
44
///
5+
/// This is the standard entry point for compiling Anchor programs in test build scripts.
6+
/// It uses default build settings without additional features.
7+
///
58
/// # Arguments
69
///
710
/// * `program_path` - The path to the anchor program. (contains Anchor.toml, Cargo.toml and src/ directory)
811
///
12+
/// For custom feature configurations, use [`build_anchor_program_with_features`].
913
pub fn build_anchor_program<P: AsRef<Path>>(program_path: P) {
10-
let program_manifest = program_path.as_ref().join("Cargo.toml");
11-
let program_src = program_path.as_ref().join("src");
12-
13-
// Tell cargo to rerun this build script if the program source changes
14-
println!("cargo:rerun-if-changed={}", program_manifest.display());
15-
println!("cargo:rerun-if-changed={}", program_src.display());
16-
17-
// Build the anchor program
18-
let output = Command::new("cargo")
19-
.args([
20-
"build-sbf",
21-
"--manifest-path",
22-
&program_manifest.to_string_lossy(),
23-
])
24-
.output();
14+
build_anchor_program_with_features(program_path, &[]);
15+
}
2516

26-
match output {
27-
Ok(output) => {
28-
if !output.status.success() {
29-
eprintln!("Failed to build anchor program:");
30-
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
31-
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
32-
std::process::exit(1);
33-
}
34-
}
35-
Err(e) => {
36-
eprintln!("Failed to execute cargo build-sbf: {}", e);
37-
eprintln!("Make sure you have the solana CLI tools installed and in your PATH");
38-
std::process::exit(1);
39-
}
40-
}
17+
/// Build an anchor program from a given path with specific features.
18+
///
19+
/// This function provides fine-grained control over which features are enabled during
20+
/// Anchor program compilation.
21+
///
22+
/// # Arguments
23+
///
24+
/// * `program_path` - The path to the anchor program. (contains Anchor.toml, Cargo.toml and src/ directory)
25+
/// * `features` - Array of feature names to enable during compilation
26+
///
27+
pub fn build_anchor_program_with_features<P: AsRef<Path>>(program_path: P, features: &[&str]) {
28+
crate::build_solana_program_internal(program_path, features);
4129
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// // Core trait
2+
// trait CuBenchInstruction { ... }
3+
4+
// // Runner
5+
// struct CuBenchRunner { ... }
6+
7+
// // Database/estimates
8+
// struct CuBenchDatabase { ... }
9+
// struct CuBenchEstimate { ... }
10+
11+
// // TX builder integration
12+
// let estimates = CuBenchDatabase::load();
13+
// let tx_builder = TxBuilder::new()
14+
// .with_cubench_estimates(estimates);
15+
16+
// // benches/cu_measurements_sol_transfer.rs
17+
// use litesvm_testing::*;
18+
19+
// #[derive(Clone, Debug, Serialize, Deserialize)]
20+
// pub struct SolTransfer {
21+
// pub amount: u64,
22+
// pub from_balance: u64,
23+
// }
24+
25+
// impl BenchmarkableInstruction for SolTransfer {
26+
// // trait implementation
27+
// }
28+
29+
// fn main() {
30+
// let mut runner = BenchmarkRunner::new();
31+
// let results = runner.benchmark_instruction::<SolTransfer>();
32+
// results.write_reports("sol_transfer").unwrap();
33+
// }

crates/litesvm-testing/src/lib.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
#[cfg(feature = "anchor")]
5353
pub mod anchor_testing;
5454

55+
#[cfg(feature = "cu_bench")]
56+
pub mod cu_bench;
57+
5558
#[cfg(feature = "pinocchio")]
5659
pub mod pinocchio_testing;
5760

@@ -573,3 +576,138 @@ pub fn setup_svm_and_fee_payer() -> (LiteSVM, Keypair) {
573576

574577
(svm, fee_payer)
575578
}
579+
580+
/// Private helper function for building Solana programs with isolated temp directories.
581+
///
582+
/// This function handles the common logic for both Anchor and Pinocchio program builds:
583+
/// - Sets up isolated temp directory to prevent file lock contention
584+
/// - Runs `cargo build-sbf` with specified features
585+
/// - Extracts workspace root from OUT_DIR environment variable
586+
/// - Copies all built .so files to workspace target directory
587+
/// - Provides aggressive error handling with clear diagnostics
588+
fn build_solana_program_internal<P: AsRef<std::path::Path>>(program_path: P, features: &[&str]) {
589+
use std::{fs, process::Command};
590+
591+
let program_manifest = program_path.as_ref().join("Cargo.toml");
592+
let program_src = program_path.as_ref().join("src");
593+
594+
// Tell cargo to rerun this build script if the program source changes
595+
println!("cargo:rerun-if-changed={}", program_manifest.display());
596+
println!("cargo:rerun-if-changed={}", program_src.display());
597+
598+
// Extract program name from Cargo.toml path
599+
let program_name = program_path
600+
.as_ref()
601+
.file_name()
602+
.and_then(|n| n.to_str())
603+
.expect("Failed to extract program name from path");
604+
605+
// Determine target directory - use existing CARGO_TARGET_DIR or create temp
606+
let base_target_dir = std::env::var("CARGO_TARGET_DIR")
607+
.map(|p| std::path::PathBuf::from(p))
608+
.unwrap_or_else(|_| std::env::temp_dir().join("litesvm-builds"));
609+
610+
let temp_dir = base_target_dir.join(format!("program-{}", program_name));
611+
612+
if let Err(e) = fs::create_dir_all(&temp_dir) {
613+
eprintln!("Failed to create build directory: {}", e);
614+
std::process::exit(1);
615+
}
616+
617+
// Build the program in isolated directory
618+
let output = Command::new("cargo")
619+
.args([
620+
"build-sbf",
621+
"--manifest-path",
622+
&program_manifest.to_string_lossy(),
623+
"--features",
624+
&features.join(","),
625+
])
626+
.env("CARGO_TARGET_DIR", &temp_dir)
627+
.output();
628+
629+
match output {
630+
Ok(output) => {
631+
if !output.status.success() {
632+
eprintln!("Failed to build program:");
633+
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
634+
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
635+
std::process::exit(1);
636+
}
637+
}
638+
Err(e) => {
639+
eprintln!("Failed to execute cargo build-sbf: {}", e);
640+
eprintln!("Make sure you have the solana CLI tools installed and in your PATH");
641+
std::process::exit(1);
642+
}
643+
}
644+
645+
// Copy all built .so files to the workspace target directory
646+
let temp_so_dir = temp_dir.join("sbf-solana-solana/release");
647+
648+
// Use OUT_DIR to find workspace target directory
649+
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build scripts");
650+
651+
// OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out
652+
// Extract workspace root and construct target path
653+
let target_pos = out_dir.find("/target/").unwrap_or_else(|| {
654+
eprintln!("FATAL: Could not find '/target/' in OUT_DIR: {}", out_dir);
655+
eprintln!("Expected OUT_DIR pattern: /workspace/target/debug/build/crate-hash/out");
656+
eprintln!("This indicates a problem with the cargo build environment.");
657+
std::process::exit(1);
658+
});
659+
660+
let workspace_root = &out_dir[..target_pos];
661+
let workspace_target = std::path::PathBuf::from(format!(
662+
"{}/target/sbf-solana-solana/release",
663+
workspace_root
664+
));
665+
666+
if let Err(e) = fs::create_dir_all(&workspace_target) {
667+
eprintln!("Failed to create workspace target directory: {}", e);
668+
std::process::exit(1);
669+
}
670+
671+
// Find and copy all .so files
672+
let entries = fs::read_dir(&temp_so_dir).unwrap_or_else(|e| {
673+
eprintln!(
674+
"FATAL: Could not read temp build directory: {}",
675+
temp_so_dir.display()
676+
);
677+
eprintln!("Error: {}", e);
678+
eprintln!("This suggests the build failed or produced no output.");
679+
std::process::exit(1);
680+
});
681+
682+
let mut copied_files = 0;
683+
for entry in entries.flatten() {
684+
let path = entry.path();
685+
if path.extension().map_or(false, |ext| ext == "so") {
686+
let filename = path.file_name().expect("File should have a name");
687+
let target_path = workspace_target.join(filename);
688+
689+
if let Err(e) = fs::copy(&path, &target_path) {
690+
eprintln!(
691+
"FATAL: Failed to copy .so file from {} to {}: {}",
692+
path.display(),
693+
target_path.display(),
694+
e
695+
);
696+
std::process::exit(1);
697+
}
698+
699+
println!("Successfully built and copied: {}", target_path.display());
700+
copied_files += 1;
701+
}
702+
}
703+
704+
if copied_files == 0 {
705+
eprintln!(
706+
"FATAL: No .so files found in build output directory: {}",
707+
temp_so_dir.display()
708+
);
709+
eprintln!("The program compilation succeeded but produced no deployable artifacts.");
710+
eprintln!("Check that the program builds correctly with 'cargo build-sbf'.");
711+
std::process::exit(1);
712+
}
713+
}

crates/litesvm-testing/src/pinocchio_testing/build.rs

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
//! build_pinocchio_program("../my-pinocchio-program");
3737
//! ```
3838
39-
use std::{path::Path, process::Command};
39+
use std::path::Path;
4040

4141
/// Build a Pinocchio program from a given path with the default features.
4242
///
@@ -79,8 +79,8 @@ pub fn build_pinocchio_program<P: AsRef<Path>>(program_path: P) {
7979
/// # Build process
8080
///
8181
/// 1. **Change detection**: Registers the Cargo.toml and src/ directory for rebuild triggers
82-
/// 2. **Compilation**: Runs `cargo build-sbf` with specified features
83-
/// 3. **Output**: Places compiled `.so` file in `target/deploy/` directory
82+
/// 2. **Compilation**: Runs `cargo build-sbf` with specified features in a temp directory
83+
/// 3. **Output**: Copies compiled `.so` file to `target/sbf-solana-solana/release/` directory
8484
/// 4. **Error handling**: Provides detailed error messages for build failures
8585
///
8686
/// # Example
@@ -111,37 +111,5 @@ pub fn build_pinocchio_program<P: AsRef<Path>>(program_path: P) {
111111
/// sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
112112
/// ```
113113
pub fn build_pinocchio_program_with_features<P: AsRef<Path>>(program_path: P, features: &[&str]) {
114-
let program_manifest = program_path.as_ref().join("Cargo.toml");
115-
let program_src = program_path.as_ref().join("src");
116-
117-
// Tell cargo to rerun this build script if the program source changes
118-
println!("cargo:rerun-if-changed={}", program_manifest.display());
119-
println!("cargo:rerun-if-changed={}", program_src.display());
120-
121-
// Build the pinocchio program
122-
let output = Command::new("cargo")
123-
.args([
124-
"build-sbf",
125-
"--manifest-path",
126-
&program_manifest.to_string_lossy(),
127-
"--features",
128-
&features.join(","),
129-
])
130-
.output();
131-
132-
match output {
133-
Ok(output) => {
134-
if !output.status.success() {
135-
eprintln!("Failed to build pinocchio program:");
136-
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
137-
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
138-
std::process::exit(1);
139-
}
140-
}
141-
Err(e) => {
142-
eprintln!("Failed to execute cargo build-sbf: {}", e);
143-
eprintln!("Make sure you have the solana CLI tools installed and in your PATH");
144-
std::process::exit(1);
145-
}
146-
}
114+
crate::build_solana_program_internal(program_path, features);
147115
}

examples/anchor/simple-anchor-tests/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@
3535
/// This works in conjunction with:
3636
/// - `build.rs` - Triggers program compilation via `litesvm_testing::anchor_testing::build_anchor_program`
3737
/// - `simple_anchor_program::ID` - The program's declared public key
38-
/// - `target/deploy/` - Solana's standard output location for compiled programs
38+
/// - `target/sbf-solana-solana/release/` - Solana's standard output location for compiled programs
3939
///
4040
/// For more complex scenarios, see `litesvm_testing::anchor_testing` for the underlying build utilities.
4141
pub fn load_simple_anchor_program(svm: &mut litesvm::LiteSVM) {
4242
svm.add_program(
4343
simple_anchor_program::ID,
4444
include_bytes!(concat!(
4545
std::env!("CARGO_MANIFEST_DIR"),
46-
"/../../../target/deploy/simple_anchor_program.so"
47-
)),
46+
"/../../../target/sbf-solana-solana/release/simple_anchor_program.so"
47+
),),
4848
);
4949
}

examples/pinocchio/simple-pinocchio-tests/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
/// This works in conjunction with:
5151
/// - `build.rs` - Triggers program compilation via `litesvm_testing::pinocchio_testing::build_pinocchio_program`
5252
/// - `simple_pinocchio_program::ID` - The program's declared public key
53-
/// - `target/deploy/` - Solana's standard output location for compiled programs
53+
/// - `target/sbf-solana-solana/release/` - Solana's standard output location for compiled programs
5454
/// - `bpf-entrypoint` feature - Required for Pinocchio BPF compilation
5555
///
5656
/// 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) {
5959
simple_pinocchio_program::ID.into(),
6060
include_bytes!(concat!(
6161
std::env!("CARGO_MANIFEST_DIR"),
62-
"/../../../target/deploy/simple_pinocchio_program.so"
62+
"/../../../target/sbf-solana-solana/release/simple_pinocchio_program.so"
6363
)),
6464
);
6565
}

0 commit comments

Comments
 (0)