use json_compilation_db::Entry;
use serde::{Deserialize, Serialize, Serializer};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub type PathMappings = (HashMap<PathBuf, PathBuf>, HashMap<PathBuf, PathBuf>);
#[derive(Error, Debug)]
pub enum CompilationDatabaseError {
#[error("Compilation database file not found: {path}")]
FileNotFound { path: String },
#[error("Failed to read compilation database file: {error}")]
ReadError { error: String },
#[error("Failed to parse compilation database JSON: {error}")]
ParseError { error: String },
#[error("Compilation database is empty")]
EmptyDatabase,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CompilationDatabase {
pub path: PathBuf,
#[serde(skip)]
pub entries: Vec<Entry>,
}
impl CompilationDatabase {
pub fn new(path: PathBuf) -> Result<Self, CompilationDatabaseError> {
if !path.exists() {
return Err(CompilationDatabaseError::FileNotFound {
path: path.to_string_lossy().to_string(),
});
}
let file = std::fs::File::open(&path).map_err(|e| CompilationDatabaseError::ReadError {
error: e.to_string(),
})?;
let reader = std::io::BufReader::new(file);
let entries: Vec<Entry> =
serde_json::from_reader(reader).map_err(|e| CompilationDatabaseError::ParseError {
error: e.to_string(),
})?;
if entries.is_empty() {
return Err(CompilationDatabaseError::EmptyDatabase);
}
Ok(Self { path, entries })
}
#[cfg(test)]
pub fn from_entries(entries: Vec<Entry>) -> Self {
Self {
path: PathBuf::from("/test/compile_commands.json"),
entries,
}
}
#[allow(dead_code)]
pub fn entries(&self) -> &[Entry] {
&self.entries
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn canonical_source_files(&self) -> Result<Vec<PathBuf>, CompilationDatabaseError> {
let mut canonical_files = Vec::new();
let mut seen_files = std::collections::HashSet::new();
for entry in &self.entries {
let canonical_path = self.canonicalize_entry_path(&entry.file)?;
if seen_files.insert(canonical_path.clone()) {
canonical_files.push(canonical_path);
}
}
canonical_files.sort();
Ok(canonical_files)
}
pub fn path_mappings(&self) -> Result<PathMappings, CompilationDatabaseError> {
let mut original_to_canonical = HashMap::new();
let mut canonical_to_original = HashMap::new();
for entry in &self.entries {
let original_path = entry.file.clone();
let canonical_path = self.canonicalize_entry_path(&entry.file)?;
original_to_canonical.insert(original_path.clone(), canonical_path.clone());
canonical_to_original.insert(canonical_path, original_path);
}
Ok((original_to_canonical, canonical_to_original))
}
fn canonicalize_entry_path(
&self,
entry_path: &Path,
) -> Result<PathBuf, CompilationDatabaseError> {
let compilation_db_dir = self.path.parent().unwrap_or_else(|| Path::new("."));
let resolved_path = if entry_path.is_relative() {
compilation_db_dir.join(entry_path)
} else {
entry_path.to_path_buf()
};
match resolved_path.canonicalize() {
Ok(canonical) => Ok(canonical),
Err(_) => {
Ok(resolved_path)
}
}
}
}
impl Serialize for CompilationDatabase {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.path.serialize(serializer)
}
}