use anyhow::{Context, Result};
use super::{BuildConfig, BuildResult, ProgramBuilder};
pub struct SolanaBuilder {}
impl SolanaBuilder {
pub fn new() -> Self {
Self {}
}
}
impl Default for SolanaBuilder {
fn default() -> Self {
Self::new()
}
}
impl ProgramBuilder for SolanaBuilder {
fn validate(&self) -> Result<()> {
if !check_solana_installed() {
return Err(anyhow::anyhow!(
"Solana CLI not found. Please install Solana CLI to enable program compilation."
));
}
Ok(())
}
fn build(&self, config: &BuildConfig) -> Result<BuildResult> {
crate::validate_program_path(&config.program_path)?;
let package_name = match get_package_name(&config.program_path) {
Ok(name) => name,
Err(e) => {
println!("cargo:warning=Failed to get package name: {e}");
"<Unknown>".into()
}
};
let dir_name = config
.program_path
.file_name()
.context("Failed to get directory name")?
.to_str()
.context("Failed to convert directory name to string")?;
let out_dir = config.output_dir.join(format!("{dir_name}-program"));
std::fs::create_dir_all(&out_dir)
.with_context(|| format!("Failed to create output directory {}", out_dir.display()))?;
let out_dir = out_dir.canonicalize().with_context(|| {
format!(
"Failed to canonicalize output directory {}",
out_dir.display()
)
})?;
let mut command = std::process::Command::new("cargo");
command
.current_dir(&config.program_path)
.arg("build-sbf")
.arg("--sbf-out-dir")
.arg(&out_dir);
if has_implementation_feature(&config.program_path)? {
command
.arg("--features")
.arg(rialo_venus_dsl::generate::constants::IMPLEMENTATION_FEATURE);
}
command
.arg("--")
.arg("--target-dir")
.arg(&config.target_dir);
let build_status = command
.status()
.with_context(|| "Failed to execute cargo build-sbf")?;
if !build_status.success() {
return Err(anyhow::anyhow!("cargo build-sbf execution failed"));
}
cleanup_temps(&config.program_path)?;
Ok(BuildResult {
package_name: package_name.clone(),
output_dir: out_dir.clone(),
program_binary: out_dir.join(format!("{package_name}.so")),
program_keypair: Some(out_dir.join(format!("{package_name}-keypair.json"))),
})
}
}
fn check_solana_installed() -> bool {
which::which("solana").is_ok()
}
fn has_implementation_feature(dir: &std::path::Path) -> Result<bool> {
let cargo_toml_path = dir.join("Cargo.toml");
if !cargo_toml_path.exists() {
return Err(anyhow::anyhow!(
"Cargo.toml not found at: {}",
cargo_toml_path.display()
));
}
let manifest = cargo_toml::Manifest::from_path(cargo_toml_path)?;
let features = manifest.features;
let has_implementation_feature =
features.contains_key(rialo_venus_dsl::generate::constants::IMPLEMENTATION_FEATURE);
Ok(has_implementation_feature)
}
fn get_package_name(dir: &std::path::Path) -> Result<String> {
let dir = dir
.canonicalize()
.with_context(|| format!("Failed to canonicalize directory {}", dir.display()))?;
let manifest_path = dir.join("Cargo.toml");
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
.with_context(|| format!("Failed to parse Cargo.toml at {}", manifest_path.display()))?;
Ok(match metadata.workspace_default_packages().first() {
Some(p) => p.name.clone(),
None => "<unknown>".into(),
})
}
fn cleanup_temps(dir: &std::path::Path) -> Result<()> {
let paths_to_clean = ["target", "elf"];
for path_name in paths_to_clean.iter() {
let path = dir.join(path_name);
if path.exists() {
if let Err(e) = std::fs::remove_dir_all(&path) {
eprintln!("Warning: Failed to remove {}: {}", path.display(), e);
}
}
}
cleanup_files(dir, &[".so", ".json"])?;
Ok(())
}
fn cleanup_files(dir: &std::path::Path, extensions: &[&str]) -> Result<()> {
if !dir.exists() {
return Ok(());
}
match std::fs::read_dir(dir) {
Ok(entries) => {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy();
if extensions.iter().any(|e| *e == format!(".{ext_str}")) {
if let Err(e) = std::fs::remove_file(&path) {
eprintln!("Warning: Failed to remove {}: {}", path.display(), e);
}
}
}
}
}
}
Err(e) => {
eprintln!("Warning: Failed to read directory {}: {}", dir.display(), e);
}
}
Ok(())
}