use crate::project::{ProjectComponent, ProjectComponentProvider, ProjectError};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
pub struct MesonProvider;
impl MesonProvider {
pub fn new() -> Self {
Self
}
fn parse_meson_buildoptions(
&self,
meson_info_dir: &Path,
) -> Result<HashMap<String, String>, ProjectError> {
let buildoptions_file = meson_info_dir.join("intro-buildoptions.json");
if !buildoptions_file.exists() {
return Ok(HashMap::new());
}
let content = fs::read_to_string(&buildoptions_file).map_err(ProjectError::Io)?;
let buildoptions: serde_json::Value =
serde_json::from_str(&content).map_err(|e| ProjectError::ParseError {
reason: format!("Failed to parse intro-buildoptions.json: {e}"),
})?;
let mut options = HashMap::new();
if let Some(options_array) = buildoptions.as_array() {
for option in options_array {
if let (Some(name), Some(value)) = (
option.get("name").and_then(|v| v.as_str()),
option.get("value").and_then(|v| v.as_str()),
) {
options.insert(name.to_string(), value.to_string());
}
}
}
Ok(options)
}
fn parse_meson_projectinfo(
&self,
meson_info_dir: &Path,
) -> Result<Option<PathBuf>, ProjectError> {
let projectinfo_file = meson_info_dir.join("intro-projectinfo.json");
if !projectinfo_file.exists() {
return Ok(None);
}
let content = fs::read_to_string(&projectinfo_file).map_err(ProjectError::Io)?;
let projectinfo: serde_json::Value =
serde_json::from_str(&content).map_err(|e| ProjectError::ParseError {
reason: format!("Failed to parse intro-projectinfo.json: {e}"),
})?;
if let Some(source_dir) = projectinfo.get("source_dir").and_then(|v| v.as_str()) {
Ok(Some(PathBuf::from(source_dir)))
} else {
Ok(None)
}
}
fn parse_meson_buildsystem_files(
&self,
meson_info_dir: &Path,
) -> Result<Option<PathBuf>, ProjectError> {
let buildsystem_files = meson_info_dir.join("intro-buildsystem_files.json");
if !buildsystem_files.exists() {
return Ok(None);
}
let content = fs::read_to_string(&buildsystem_files).map_err(ProjectError::Io)?;
let files: serde_json::Value =
serde_json::from_str(&content).map_err(|e| ProjectError::ParseError {
reason: format!("Failed to parse intro-buildsystem_files.json: {e}"),
})?;
if let Some(files_array) = files.as_array() {
for file_value in files_array {
if let Some(file_path) = file_value.as_str() {
let path = PathBuf::from(file_path);
if path.file_name().and_then(|n| n.to_str()) == Some("meson.build")
&& let Some(parent) = path.parent()
{
return Ok(Some(parent.to_path_buf()));
}
}
}
}
Ok(None)
}
fn find_compilation_database(&self, build_dir: &Path) -> Option<PathBuf> {
let compile_commands = build_dir.join("compile_commands.json");
if compile_commands.exists() {
Some(compile_commands)
} else {
None
}
}
fn extract_build_options(
&self,
meson_info_dir: &Path,
) -> Result<HashMap<String, String>, ProjectError> {
let mut build_options = self.parse_meson_buildoptions(meson_info_dir)?;
build_options.insert("BUILD_SYSTEM".to_string(), "meson".to_string());
if let Some(buildtype) = build_options.get("buildtype") {
build_options.insert("BUILD_TYPE".to_string(), buildtype.clone());
}
Ok(build_options)
}
}
impl ProjectComponentProvider for MesonProvider {
fn scan_path(&self, path: &Path) -> Result<Option<ProjectComponent>, ProjectError> {
let meson_info_dir = path.join("meson-info");
if !meson_info_dir.exists() || !meson_info_dir.is_dir() {
return Ok(None);
}
let build_options = self.extract_build_options(&meson_info_dir)?;
let source_root = if let Some(source_dir) = self.parse_meson_projectinfo(&meson_info_dir)? {
source_dir
} else if let Some(source_dir) = self.parse_meson_buildsystem_files(&meson_info_dir)? {
source_dir
} else {
path.parent()
.ok_or_else(|| ProjectError::SourceRootNotFound {
path: path.to_string_lossy().to_string(),
})?
.to_path_buf()
};
let compilation_database_path = self.find_compilation_database(path).ok_or_else(|| {
ProjectError::CompilationDatabaseNotFound {
path: path
.join("compile_commands.json")
.to_string_lossy()
.to_string(),
}
})?;
let generator = build_options
.get("backend")
.unwrap_or(&"ninja".to_string())
.clone();
let build_type = build_options
.get("buildtype")
.unwrap_or(&"debug".to_string())
.clone();
let component = ProjectComponent::new(
path.to_path_buf(),
source_root,
compilation_database_path,
"meson".to_string(),
generator,
build_type,
build_options,
)?;
Ok(Some(component))
}
}
impl Default for MesonProvider {
fn default() -> Self {
Self::new()
}
}