Skip to main content

rialo_build_lib/
detection.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Program type detection and auto-configuration
5
6use std::path::Path;
7
8use anyhow::{Context, Result};
9
10/// Detected program type
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ProgramType {
13    /// Solana program (Cargo.toml with solana-program dependency)
14    Solana,
15    /// RISC-V C program (C source files)
16    RiscvC,
17    /// RISC-V Rust program (Cargo.toml with RISC-V target or no solana deps)
18    RiscvRust,
19}
20
21/// Detect the program type from a directory
22pub fn detect_program_type(path: &Path) -> Result<ProgramType> {
23    // Validate program path exists (backwards compatibility)
24    crate::validate_program_path(path)?;
25
26    // Check for Cargo.toml first
27    let cargo_toml = path.join("Cargo.toml");
28
29    if cargo_toml.exists() {
30        // Try to determine if it's a Solana or RISC-V Rust program
31        if is_solana_program(&cargo_toml)? {
32            return Ok(ProgramType::Solana);
33        } else {
34            // If it has Cargo.toml but no Solana dependencies, assume RISC-V Rust
35            return Ok(ProgramType::RiscvRust);
36        }
37    }
38
39    // Check for C source files
40    if has_c_sources(path)? {
41        return Ok(ProgramType::RiscvC);
42    }
43
44    Err(anyhow::anyhow!(
45        "Could not detect program type in {}. Directory should contain:\n\
46         - Cargo.toml (for Solana or RISC-V Rust programs)\n\
47         - C source files (.c or .S) (for RISC-V C programs)",
48        path.display()
49    ))
50}
51
52/// Check if a Cargo.toml represents a Solana program
53fn is_solana_program(cargo_toml_path: &Path) -> Result<bool> {
54    let manifest = cargo_toml::Manifest::from_path(cargo_toml_path).with_context(|| {
55        format!(
56            "Failed to parse Cargo.toml at {}",
57            cargo_toml_path.display()
58        )
59    })?;
60
61    // Check if any dependency is a known Solana crate
62    let solana_deps = [
63        "solana-program",
64        "solana-sdk",
65        "rialo-s-program",
66        "rialo-s-sdk",
67    ];
68
69    // Check regular dependencies
70    for dep_name in solana_deps {
71        if manifest.dependencies.contains_key(dep_name) {
72            return Ok(true);
73        }
74    }
75
76    // Check dev dependencies
77    for dep_name in solana_deps {
78        if manifest.dev_dependencies.contains_key(dep_name) {
79            return Ok(true);
80        }
81    }
82
83    Ok(false)
84}
85
86/// Check if a directory contains C source files
87fn has_c_sources(path: &Path) -> Result<bool> {
88    // Check root directory
89    if has_c_files_in_dir(path)? {
90        return Ok(true);
91    }
92
93    // Check src subdirectory
94    let src_dir = path.join("src");
95    if src_dir.exists() && has_c_files_in_dir(&src_dir)? {
96        return Ok(true);
97    }
98
99    Ok(false)
100}
101
102/// Check if a specific directory contains C files
103fn has_c_files_in_dir(dir: &Path) -> Result<bool> {
104    if !dir.is_dir() {
105        return Ok(false);
106    }
107
108    let entries = std::fs::read_dir(dir)
109        .with_context(|| format!("Failed to read directory {}", dir.display()))?;
110
111    for entry in entries.flatten() {
112        let path = entry.path();
113        if path.is_file() {
114            if let Some(ext) = path.extension() {
115                if ext == "c" || ext == "S" {
116                    return Ok(true);
117                }
118            }
119        }
120    }
121
122    Ok(false)
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_program_type_equality() {
131        assert_eq!(ProgramType::Solana, ProgramType::Solana);
132        assert_ne!(ProgramType::Solana, ProgramType::RiscvC);
133        assert_ne!(ProgramType::RiscvC, ProgramType::RiscvRust);
134    }
135}