rialo-build-lib 0.10.1

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

//! Program type detection and auto-configuration

use std::path::Path;

use anyhow::{Context, Result};

/// Detected program type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProgramType {
    /// Solana program (Cargo.toml with solana-program dependency)
    Solana,
    /// RISC-V C program (C source files)
    RiscvC,
    /// RISC-V Rust program (Cargo.toml with RISC-V target or no solana deps)
    RiscvRust,
}

/// Detect the program type from a directory
pub fn detect_program_type(path: &Path) -> Result<ProgramType> {
    // Validate program path exists (backwards compatibility)
    crate::validate_program_path(path)?;

    // Check for Cargo.toml first
    let cargo_toml = path.join("Cargo.toml");

    if cargo_toml.exists() {
        // Try to determine if it's a Solana or RISC-V Rust program
        if is_solana_program(&cargo_toml)? {
            return Ok(ProgramType::Solana);
        } else {
            // If it has Cargo.toml but no Solana dependencies, assume RISC-V Rust
            return Ok(ProgramType::RiscvRust);
        }
    }

    // Check for C source files
    if has_c_sources(path)? {
        return Ok(ProgramType::RiscvC);
    }

    Err(anyhow::anyhow!(
        "Could not detect program type in {}. Directory should contain:\n\
         - Cargo.toml (for Solana or RISC-V Rust programs)\n\
         - C source files (.c or .S) (for RISC-V C programs)",
        path.display()
    ))
}

/// Check if a Cargo.toml represents a Solana program
fn is_solana_program(cargo_toml_path: &Path) -> Result<bool> {
    let manifest = cargo_toml::Manifest::from_path(cargo_toml_path).with_context(|| {
        format!(
            "Failed to parse Cargo.toml at {}",
            cargo_toml_path.display()
        )
    })?;

    // Check if any dependency is a known Solana crate
    let solana_deps = [
        "solana-program",
        "solana-sdk",
        "rialo-s-program",
        "rialo-s-sdk",
    ];

    // Check regular dependencies
    for dep_name in solana_deps {
        if manifest.dependencies.contains_key(dep_name) {
            return Ok(true);
        }
    }

    // Check dev dependencies
    for dep_name in solana_deps {
        if manifest.dev_dependencies.contains_key(dep_name) {
            return Ok(true);
        }
    }

    Ok(false)
}

/// Check if a directory contains C source files
fn has_c_sources(path: &Path) -> Result<bool> {
    // Check root directory
    if has_c_files_in_dir(path)? {
        return Ok(true);
    }

    // Check src subdirectory
    let src_dir = path.join("src");
    if src_dir.exists() && has_c_files_in_dir(&src_dir)? {
        return Ok(true);
    }

    Ok(false)
}

/// Check if a specific directory contains C files
fn has_c_files_in_dir(dir: &Path) -> Result<bool> {
    if !dir.is_dir() {
        return Ok(false);
    }

    let entries = std::fs::read_dir(dir)
        .with_context(|| format!("Failed to read directory {}", dir.display()))?;

    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_file() {
            if let Some(ext) = path.extension() {
                if ext == "c" || ext == "S" {
                    return Ok(true);
                }
            }
        }
    }

    Ok(false)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_program_type_equality() {
        assert_eq!(ProgramType::Solana, ProgramType::Solana);
        assert_ne!(ProgramType::Solana, ProgramType::RiscvC);
        assert_ne!(ProgramType::RiscvC, ProgramType::RiscvRust);
    }
}