use std::path::Path;
use anyhow::{Context, Result};
use crate::RiscvTarget;
#[derive(Debug, Clone, Default)]
pub struct BuildFileConfig {
pub build_type: Option<BuildType>,
pub riscv: Option<RiscvConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildType {
Solana,
Riscv,
Auto,
}
#[derive(Debug, Clone)]
pub struct RiscvConfig {
pub target: Option<RiscvTarget>,
pub toolchain_version: Option<String>,
pub source_type: Option<SourceType>,
pub compile_flags: Option<CompileFlags>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceType {
C,
Rust,
}
#[derive(Debug, Clone)]
pub struct CompileFlags {
pub optimization: Option<String>,
pub march: Option<String>,
pub mabi: Option<String>,
pub additional_flags: Vec<String>,
}
impl BuildFileConfig {
pub fn from_file(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config file {}", path.display()))?;
Self::parse_str(&content)
}
pub fn parse_str(content: &str) -> Result<Self> {
let toml_value: toml::Value =
toml::from_str(content).context("Failed to parse TOML configuration")?;
let mut config = BuildFileConfig::default();
if let Some(build_section) = toml_value.get("build") {
if let Some(build_type) = build_section.get("type") {
if let Some(type_str) = build_type.as_str() {
config.build_type = Some(parse_build_type(type_str)?);
}
}
}
if let Some(riscv_section) = toml_value.get("riscv") {
config.riscv = Some(parse_riscv_config(riscv_section)?);
}
Ok(config)
}
pub fn from_directory(dir: &Path) -> Result<Option<Self>> {
let config_path = dir.join("rialo-build.toml");
if !config_path.exists() {
return Ok(None);
}
Ok(Some(Self::from_file(&config_path)?))
}
}
fn parse_build_type(s: &str) -> Result<BuildType> {
match s.to_lowercase().as_str() {
"solana" => Ok(BuildType::Solana),
"riscv" => Ok(BuildType::Riscv),
"auto" => Ok(BuildType::Auto),
_ => Err(anyhow::anyhow!(
"Invalid build type: {}. Must be 'solana', 'riscv', or 'auto'",
s
)),
}
}
fn parse_riscv_config(value: &toml::Value) -> Result<RiscvConfig> {
let mut config = RiscvConfig {
target: None,
toolchain_version: None,
source_type: None,
compile_flags: None,
};
if let Some(target_str) = value.get("target").and_then(|v| v.as_str()) {
config.target = Some(parse_riscv_target(target_str)?);
}
if let Some(version) = value.get("toolchain_version").and_then(|v| v.as_str()) {
config.toolchain_version = Some(version.to_string());
}
if let Some(source_type_str) = value.get("source_type").and_then(|v| v.as_str()) {
config.source_type = Some(parse_source_type(source_type_str)?);
}
if let Some(compile_flags_section) = value.get("compile_flags") {
config.compile_flags = Some(parse_compile_flags(compile_flags_section)?);
}
Ok(config)
}
fn parse_riscv_target(s: &str) -> Result<RiscvTarget> {
match s.to_lowercase().as_str() {
"rv32i" => Ok(RiscvTarget::Rv32i),
"rv32im" => Ok(RiscvTarget::Rv32im),
"rv64gc" => Ok(RiscvTarget::Rv64gc),
_ => Err(anyhow::anyhow!(
"Invalid RISC-V target: {}. Must be 'rv32i', 'rv32im', or 'rv64gc'",
s
)),
}
}
fn parse_source_type(s: &str) -> Result<SourceType> {
match s.to_lowercase().as_str() {
"c" => Ok(SourceType::C),
"rust" => Ok(SourceType::Rust),
_ => Err(anyhow::anyhow!(
"Invalid source type: {}. Must be 'c' or 'rust'",
s
)),
}
}
fn parse_compile_flags(value: &toml::Value) -> Result<CompileFlags> {
let mut flags = CompileFlags {
optimization: None,
march: None,
mabi: None,
additional_flags: Vec::new(),
};
if let Some(opt) = value.get("optimization").and_then(|v| v.as_str()) {
flags.optimization = Some(opt.to_string());
}
if let Some(march) = value.get("march").and_then(|v| v.as_str()) {
flags.march = Some(march.to_string());
}
if let Some(mabi) = value.get("mabi").and_then(|v| v.as_str()) {
flags.mabi = Some(mabi.to_string());
}
if let Some(additional) = value.get("additional_flags").and_then(|v| v.as_array()) {
for flag in additional {
if let Some(flag_str) = flag.as_str() {
flags.additional_flags.push(flag_str.to_string());
}
}
}
Ok(flags)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_basic_config() {
let config_str = r#"
[build]
type = "riscv"
[riscv]
target = "rv64gc"
toolchain_version = "13.2.0"
"#;
let config = BuildFileConfig::parse_str(config_str).unwrap();
assert_eq!(config.build_type, Some(BuildType::Riscv));
assert!(config.riscv.is_some());
let riscv = config.riscv.unwrap();
assert_eq!(riscv.target, Some(RiscvTarget::Rv64gc));
assert_eq!(riscv.toolchain_version, Some("13.2.0".to_string()));
}
#[test]
fn test_parse_compile_flags() {
let config_str = r#"
[build]
type = "riscv"
[riscv]
target = "rv32im"
[riscv.compile_flags]
optimization = "O2"
march = "rv32im"
mabi = "ilp32"
additional_flags = ["-static", "-nostdlib"]
"#;
let config = BuildFileConfig::parse_str(config_str).unwrap();
let riscv = config.riscv.unwrap();
let flags = riscv.compile_flags.unwrap();
assert_eq!(flags.optimization, Some("O2".to_string()));
assert_eq!(flags.march, Some("rv32im".to_string()));
assert_eq!(flags.mabi, Some("ilp32".to_string()));
assert_eq!(flags.additional_flags.len(), 2);
}
#[test]
fn test_parse_auto_type() {
let config_str = r#"
[build]
type = "auto"
"#;
let config = BuildFileConfig::parse_str(config_str).unwrap();
assert_eq!(config.build_type, Some(BuildType::Auto));
}
}