rialo-build-lib 0.2.0

Shared library for Rialo program building logic
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::path::PathBuf;

use anyhow::Result;

pub mod config;
pub mod detection;
mod riscv_builder;
mod solana_builder;
pub mod toolchain;

pub use config::{BuildFileConfig, BuildType, CompileFlags, RiscvConfig, SourceType};
pub use detection::{detect_program_type, ProgramType};
pub use riscv_builder::RiscvBuilder;
pub use solana_builder::SolanaBuilder;
pub use toolchain::{
    BuildSystemConfig, DownloadSource, GnuRiscvToolchain, RialoRustToolchain, RustSourceBuilder,
    S3StorageBackend, SourceBuildConfig, SourceBuildable, Toolchain, ToolchainConfig,
    ToolchainType,
};

/// Configuration for building a Rialo program
#[derive(Debug, Clone)]
pub struct BuildConfig {
    /// Path to the program directory to build
    pub program_path: PathBuf,
    /// Output directory for the built artifacts
    pub output_dir: PathBuf,
    /// Target directory for cargo build artifacts
    pub target_dir: PathBuf,
}

/// Validate that a program path exists and is a directory
///
/// Returns an error if the path does not exist or is not a directory.
/// This is used to provide consistent error messages across all builders.
pub fn validate_program_path(path: &std::path::Path) -> Result<()> {
    if !path.exists() {
        return Err(anyhow::anyhow!(
            "Program path does not exist: {}",
            path.display()
        ));
    }

    if !path.is_dir() {
        return Err(anyhow::anyhow!(
            "Program path is not a directory: {}",
            path.display()
        ));
    }

    Ok(())
}

/// RISC-V target architecture
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RiscvTarget {
    /// RV32I base integer instruction set
    Rv32i,
    /// RV32IM with integer multiply/divide
    Rv32im,
    /// RV64GC general purpose (includes IMAFD + Zicsr + Zifencei + C extensions)
    Rv64gc,
    /// Rialo custom target (riscv64emac-solana-solana) with custom Rust toolchain
    #[default]
    RialoCustom,
}

impl RiscvTarget {
    /// Get the target triple string for cargo
    pub fn as_target_triple(&self) -> &str {
        match self {
            RiscvTarget::Rv32i => "riscv32i-unknown-none-elf",
            RiscvTarget::Rv32im => "riscv32im-unknown-none-elf",
            RiscvTarget::Rv64gc => "riscv64gc-unknown-none-elf",
            RiscvTarget::RialoCustom => "riscv64emac-solana-solana",
        }
    }

    /// Get the -march flag for gcc
    pub fn as_march(&self) -> &str {
        match self {
            RiscvTarget::Rv32i => "rv32i",
            RiscvTarget::Rv32im => "rv32im",
            RiscvTarget::Rv64gc => "rv64gc",
            RiscvTarget::RialoCustom => "rv64gc", // Fallback for C compilation
        }
    }

    /// Get the -mabi flag for gcc
    pub fn as_mabi(&self) -> &str {
        match self {
            RiscvTarget::Rv32i | RiscvTarget::Rv32im => "ilp32",
            RiscvTarget::Rv64gc | RiscvTarget::RialoCustom => "lp64d",
        }
    }

    /// Check if this target requires the Rialo custom Rust toolchain
    pub fn requires_rialo_toolchain(&self) -> bool {
        matches!(self, RiscvTarget::RialoCustom)
    }
}

/// Builder-specific configuration
#[derive(Debug, Clone)]
pub enum BuilderConfig {
    /// Configuration for the Solana builder
    Solana {},
    /// Configuration for the RISC-V builder
    Riscv {
        /// Toolchain version (optional, uses default if not specified)
        toolchain_version: Option<String>,
        /// Target architecture
        target: RiscvTarget,
    },
}

/// Result of a build operation
#[derive(Debug, serde::Serialize)]
pub struct BuildResult {
    /// The package name that was built
    pub package_name: String,
    /// The output directory where artifacts were placed
    pub output_dir: PathBuf,
    /// The program binary file
    pub program_binary: PathBuf,
    /// The program keypair file (optional, Solana-specific)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub program_keypair: Option<PathBuf>,
}

/// Trait for building Rialo programs
pub trait ProgramBuilder {
    /// Validate that the builder can be used (e.g., check dependencies)
    fn validate(&self) -> Result<()>;
    /// Build a program using the given configuration
    fn build(&self, config: &BuildConfig) -> Result<BuildResult>;
}

/// Create a builder based on the builder config
pub fn create_builder(builder_config: &BuilderConfig) -> Result<Box<dyn ProgramBuilder>> {
    match builder_config {
        BuilderConfig::Solana {} => Ok(Box::new(SolanaBuilder::default())),
        BuilderConfig::Riscv {
            toolchain_version,
            target,
        } => {
            let builder = if let Some(version) = toolchain_version {
                RiscvBuilder::with_version(version, *target)?
            } else {
                RiscvBuilder::new(*target)?
            };
            Ok(Box::new(builder))
        }
    }
}

/// Build a single Rialo program using the default builder
pub fn build_program(config: &BuildConfig) -> Result<BuildResult> {
    let builder = create_builder(&BuilderConfig::Solana {})?;
    builder.validate()?;
    builder.build(config)
}

/// Automatically detect the builder configuration based on the program directory
///
/// This function will:
/// 1. Look for a rialo-build.toml configuration file
/// 2. If not found or set to "auto", detect the program type
/// 3. Return the appropriate BuilderConfig
pub fn auto_detect_builder(program_path: &std::path::Path) -> Result<BuilderConfig> {
    // Try to load configuration file
    let file_config = BuildFileConfig::from_directory(program_path)?;

    // If we have a config file with explicit build type, use it
    if let Some(config) = &file_config {
        if let Some(build_type) = config.build_type {
            match build_type {
                BuildType::Solana => return Ok(BuilderConfig::Solana {}),
                BuildType::Riscv => {
                    let target = config
                        .riscv
                        .as_ref()
                        .and_then(|r| r.target)
                        .unwrap_or_default();

                    let toolchain_version = config
                        .riscv
                        .as_ref()
                        .and_then(|r| r.toolchain_version.clone());

                    return Ok(BuilderConfig::Riscv {
                        toolchain_version,
                        target,
                    });
                }
                BuildType::Auto => {
                    // Continue to auto-detection
                }
            }
        }
    }

    // Auto-detect based on program contents
    let program_type = detect_program_type(program_path)?;

    match program_type {
        ProgramType::Solana => Ok(BuilderConfig::Solana {}),
        ProgramType::RiscvC | ProgramType::RiscvRust => {
            // Use config file settings if available, otherwise use defaults
            let target = file_config
                .as_ref()
                .and_then(|c| c.riscv.as_ref())
                .and_then(|r| r.target)
                .unwrap_or_default();

            let toolchain_version = file_config
                .as_ref()
                .and_then(|c| c.riscv.as_ref())
                .and_then(|r| r.toolchain_version.clone());

            Ok(BuilderConfig::Riscv {
                toolchain_version,
                target,
            })
        }
    }
}

/// Build a program with automatic builder detection
pub fn build_program_auto(config: &BuildConfig) -> Result<BuildResult> {
    let builder_config = auto_detect_builder(&config.program_path)?;
    let builder = create_builder(&builder_config)?;
    builder.validate()?;
    builder.build(config)
}