rialo_build_lib/
config.rs1use std::path::Path;
7
8use anyhow::{Context, Result};
9
10use crate::RiscvTarget;
11
12#[derive(Debug, Clone, Default)]
14pub struct BuildFileConfig {
15 pub build_type: Option<BuildType>,
17 pub riscv: Option<RiscvConfig>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BuildType {
24 Solana,
26 Riscv,
28 Auto,
30}
31
32#[derive(Debug, Clone)]
34pub struct RiscvConfig {
35 pub target: Option<RiscvTarget>,
37 pub toolchain_version: Option<String>,
39 pub source_type: Option<SourceType>,
41 pub compile_flags: Option<CompileFlags>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SourceType {
48 C,
50 Rust,
52}
53
54#[derive(Debug, Clone)]
56pub struct CompileFlags {
57 pub optimization: Option<String>,
59 pub march: Option<String>,
61 pub mabi: Option<String>,
63 pub additional_flags: Vec<String>,
65}
66
67impl BuildFileConfig {
68 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 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 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 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 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}