Skip to main content

rialo_build_lib/
config.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Build configuration file support
5
6use std::path::Path;
7
8use anyhow::{Context, Result};
9
10use crate::RiscvTarget;
11
12/// Build configuration from rialo-build.toml
13#[derive(Debug, Clone, Default)]
14pub struct BuildFileConfig {
15    /// Build type (solana, riscv, or auto)
16    pub build_type: Option<BuildType>,
17    /// RISC-V specific configuration
18    pub riscv: Option<RiscvConfig>,
19}
20
21/// Build type specified in config
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BuildType {
24    /// Solana program
25    Solana,
26    /// RISC-V program
27    Riscv,
28    /// Auto-detect
29    Auto,
30}
31
32/// RISC-V build configuration
33#[derive(Debug, Clone)]
34pub struct RiscvConfig {
35    /// Target architecture
36    pub target: Option<RiscvTarget>,
37    /// Toolchain version
38    pub toolchain_version: Option<String>,
39    /// Source type (c or rust)
40    pub source_type: Option<SourceType>,
41    /// Compile flags
42    pub compile_flags: Option<CompileFlags>,
43}
44
45/// Source type for RISC-V programs
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SourceType {
48    /// C program
49    C,
50    /// Rust program
51    Rust,
52}
53
54/// Compile flags for C programs
55#[derive(Debug, Clone)]
56pub struct CompileFlags {
57    /// Optimization level (O0, O1, O2, O3, Os, Oz)
58    pub optimization: Option<String>,
59    /// Architecture (march flag)
60    pub march: Option<String>,
61    /// ABI (mabi flag)
62    pub mabi: Option<String>,
63    /// Additional flags
64    pub additional_flags: Vec<String>,
65}
66
67impl BuildFileConfig {
68    /// Load configuration from a file
69    pub fn from_file(path: &Path) -> Result<Self> {
70        let content = std::fs::read_to_string(path)
71            .with_context(|| format!("Failed to read config file {}", path.display()))?;
72
73        Self::parse_str(&content)
74    }
75
76    /// Parse configuration from a string
77    pub fn parse_str(content: &str) -> Result<Self> {
78        let toml_value: toml::Value =
79            toml::from_str(content).context("Failed to parse TOML configuration")?;
80
81        let mut config = BuildFileConfig::default();
82
83        // Parse [build] section
84        if let Some(build_section) = toml_value.get("build") {
85            if let Some(build_type) = build_section.get("type") {
86                if let Some(type_str) = build_type.as_str() {
87                    config.build_type = Some(parse_build_type(type_str)?);
88                }
89            }
90        }
91
92        // Parse [riscv] section
93        if let Some(riscv_section) = toml_value.get("riscv") {
94            config.riscv = Some(parse_riscv_config(riscv_section)?);
95        }
96
97        Ok(config)
98    }
99
100    /// Try to load configuration from a directory
101    /// Returns None if no configuration file is found
102    pub fn from_directory(dir: &Path) -> Result<Option<Self>> {
103        let config_path = dir.join("rialo-build.toml");
104
105        if !config_path.exists() {
106            return Ok(None);
107        }
108
109        Ok(Some(Self::from_file(&config_path)?))
110    }
111}
112
113fn parse_build_type(s: &str) -> Result<BuildType> {
114    match s.to_lowercase().as_str() {
115        "solana" => Ok(BuildType::Solana),
116        "riscv" => Ok(BuildType::Riscv),
117        "auto" => Ok(BuildType::Auto),
118        _ => Err(anyhow::anyhow!(
119            "Invalid build type: {}. Must be 'solana', 'riscv', or 'auto'",
120            s
121        )),
122    }
123}
124
125fn parse_riscv_config(value: &toml::Value) -> Result<RiscvConfig> {
126    let mut config = RiscvConfig {
127        target: None,
128        toolchain_version: None,
129        source_type: None,
130        compile_flags: None,
131    };
132
133    if let Some(target_str) = value.get("target").and_then(|v| v.as_str()) {
134        config.target = Some(parse_riscv_target(target_str)?);
135    }
136
137    if let Some(version) = value.get("toolchain_version").and_then(|v| v.as_str()) {
138        config.toolchain_version = Some(version.to_string());
139    }
140
141    if let Some(source_type_str) = value.get("source_type").and_then(|v| v.as_str()) {
142        config.source_type = Some(parse_source_type(source_type_str)?);
143    }
144
145    if let Some(compile_flags_section) = value.get("compile_flags") {
146        config.compile_flags = Some(parse_compile_flags(compile_flags_section)?);
147    }
148
149    Ok(config)
150}
151
152fn parse_riscv_target(s: &str) -> Result<RiscvTarget> {
153    match s.to_lowercase().as_str() {
154        "rv32i" => Ok(RiscvTarget::Rv32i),
155        "rv32im" => Ok(RiscvTarget::Rv32im),
156        "rv64gc" => Ok(RiscvTarget::Rv64gc),
157        _ => Err(anyhow::anyhow!(
158            "Invalid RISC-V target: {}. Must be 'rv32i', 'rv32im', or 'rv64gc'",
159            s
160        )),
161    }
162}
163
164fn parse_source_type(s: &str) -> Result<SourceType> {
165    match s.to_lowercase().as_str() {
166        "c" => Ok(SourceType::C),
167        "rust" => Ok(SourceType::Rust),
168        _ => Err(anyhow::anyhow!(
169            "Invalid source type: {}. Must be 'c' or 'rust'",
170            s
171        )),
172    }
173}
174
175fn parse_compile_flags(value: &toml::Value) -> Result<CompileFlags> {
176    let mut flags = CompileFlags {
177        optimization: None,
178        march: None,
179        mabi: None,
180        additional_flags: Vec::new(),
181    };
182
183    if let Some(opt) = value.get("optimization").and_then(|v| v.as_str()) {
184        flags.optimization = Some(opt.to_string());
185    }
186
187    if let Some(march) = value.get("march").and_then(|v| v.as_str()) {
188        flags.march = Some(march.to_string());
189    }
190
191    if let Some(mabi) = value.get("mabi").and_then(|v| v.as_str()) {
192        flags.mabi = Some(mabi.to_string());
193    }
194
195    if let Some(additional) = value.get("additional_flags").and_then(|v| v.as_array()) {
196        for flag in additional {
197            if let Some(flag_str) = flag.as_str() {
198                flags.additional_flags.push(flag_str.to_string());
199            }
200        }
201    }
202
203    Ok(flags)
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_parse_basic_config() {
212        let config_str = r#"
213[build]
214type = "riscv"
215
216[riscv]
217target = "rv64gc"
218toolchain_version = "13.2.0"
219"#;
220
221        let config = BuildFileConfig::parse_str(config_str).unwrap();
222        assert_eq!(config.build_type, Some(BuildType::Riscv));
223        assert!(config.riscv.is_some());
224
225        let riscv = config.riscv.unwrap();
226        assert_eq!(riscv.target, Some(RiscvTarget::Rv64gc));
227        assert_eq!(riscv.toolchain_version, Some("13.2.0".to_string()));
228    }
229
230    #[test]
231    fn test_parse_compile_flags() {
232        let config_str = r#"
233[build]
234type = "riscv"
235
236[riscv]
237target = "rv32im"
238
239[riscv.compile_flags]
240optimization = "O2"
241march = "rv32im"
242mabi = "ilp32"
243additional_flags = ["-static", "-nostdlib"]
244"#;
245
246        let config = BuildFileConfig::parse_str(config_str).unwrap();
247        let riscv = config.riscv.unwrap();
248        let flags = riscv.compile_flags.unwrap();
249
250        assert_eq!(flags.optimization, Some("O2".to_string()));
251        assert_eq!(flags.march, Some("rv32im".to_string()));
252        assert_eq!(flags.mabi, Some("ilp32".to_string()));
253        assert_eq!(flags.additional_flags.len(), 2);
254    }
255
256    #[test]
257    fn test_parse_auto_type() {
258        let config_str = r#"
259[build]
260type = "auto"
261"#;
262
263        let config = BuildFileConfig::parse_str(config_str).unwrap();
264        assert_eq!(config.build_type, Some(BuildType::Auto));
265    }
266}