use std::fs;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::{error::Error, programs::SolanaProgram};
pub fn build_programs(programs: &[SolanaProgram]) -> Result<Vec<PathBuf>, Error> {
programs.iter().map(build_program).collect()
}
pub fn build_program(program: &SolanaProgram) -> Result<PathBuf, Error> {
let sbf_out_dir = std::env::temp_dir()
.join("elf-magic-bin")
.join(program.package_name.clone());
let program_so_path = sbf_out_dir.join(format!("{}.so", program.target_name));
if program_so_path.exists() {
fs::remove_file(&program_so_path).map_err(|e| Error::ProgramBuild {
program: program.target_name.clone(),
error: format!("Failed to remove existing .so file: {}", e),
})?;
}
let status = Command::new("cargo")
.args([
"build-sbf",
"--manifest-path",
&program.manifest_path.to_string_lossy(),
"--sbf-out-dir",
&sbf_out_dir.to_string_lossy(),
])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| Error::ProgramBuild {
program: program.target_name.clone(),
error: format!(
"Failed to execute cargo build-sbf: {}\nMake sure solana CLI tools are installed",
e
),
})?;
if !status.success() {
return Err(Error::ProgramBuild {
program: program.target_name.clone(),
error: format!("cargo build-sbf failed with exit code: {:?}", status.code()),
});
}
if !program_so_path.exists() {
return Err(Error::ProgramBuild {
program: program.target_name.clone(),
error: format!(
"Expected .so file not found at: {}",
program_so_path.display()
),
});
}
println!(
"cargo:rustc-env={}={}",
program.env_var_name(),
program_so_path.display()
);
Ok(program_so_path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn sample_program() -> SolanaProgram {
SolanaProgram {
package_name: "test_package".to_string(),
target_name: "test_target".to_string(),
manifest_path: PathBuf::from("/workspace/Cargo.toml"),
}
}
#[test]
fn test_build_programs_empty() {
let result = build_programs(&[]);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_expected_so_file_path() {
let temp_dir = TempDir::new().unwrap();
let cargo_target_dir = temp_dir.path();
let _program = sample_program();
let expected_path = cargo_target_dir
.join("elf-magic-bin")
.join("test_target.so");
assert_eq!(expected_path.file_name().unwrap(), "test_target.so");
assert_eq!(
expected_path.parent().unwrap().file_name().unwrap(),
"elf-magic-bin"
);
}
#[test]
fn test_program_env_var_name_consistency() {
let program = sample_program();
let expected_env_var = "TEST_TARGET_ELF_PATH";
assert_eq!(program.env_var_name(), expected_env_var);
}
#[test]
fn test_build_programs_preserves_order() {
let programs = vec![
SolanaProgram {
package_name: "pkg1".to_string(),
target_name: "target1".to_string(),
manifest_path: PathBuf::from("/workspace/Cargo.toml"),
},
SolanaProgram {
package_name: "pkg2".to_string(),
target_name: "target2".to_string(),
manifest_path: PathBuf::from("/workspace/Cargo.toml"),
},
];
let result = build_programs(&programs);
match result {
Ok(paths) => {
assert_eq!(paths.len(), 2);
}
Err(_) => {
}
}
}
#[test]
fn test_sbf_out_dir_creation() {
let temp_dir = TempDir::new().unwrap();
let cargo_target_dir = temp_dir.path();
let expected_sbf_dir = cargo_target_dir.join("elf-magic-bin");
assert_eq!(expected_sbf_dir.parent(), Some(cargo_target_dir));
assert_eq!(expected_sbf_dir.file_name().unwrap(), "elf-magic-bin");
}
#[test]
fn test_program_so_filename() {
let programs = vec![
sample_program(),
SolanaProgram {
package_name: "my_package".to_string(),
target_name: "my-complex-target-name".to_string(),
manifest_path: PathBuf::from("/workspace/Cargo.toml"),
},
];
for program in &programs {
let expected_filename = format!("{}.so", program.target_name);
assert!(expected_filename.ends_with(".so"));
assert!(expected_filename.contains(&program.target_name));
}
}
}