use std::collections::HashMap;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;
use crate::extract;
use super::artifact::{write_graph, Graph, GRAPH_SCHEMA_VERSION};
use super::cache::{
read_fragments_cache, write_fragments_cache, write_fragments_index, FragmentCacheEntry,
};
use super::fragment_types::{AnyFragment, Fragment};
use super::ordered::OrderedMap;
use super::paths::{
atomic_write_json, graph_json_path, project_units_path, remove_superseded_caches,
};
#[derive(Debug, Clone)]
pub struct GraphFile {
pub rel: String,
pub mtime: i64,
}
#[derive(Debug)]
pub enum RebuildOutcome {
NotRebuilt,
Rebuilt(Graph),
}
pub(crate) fn graph_schema_is_current(root: &Path) -> bool {
let Ok(mut file) = fs::File::open(graph_json_path(root)) else {
return false;
};
let mut head = [0u8; 64];
let mut filled = 0;
while filled < head.len() {
match file.read(&mut head[filled..]) {
Ok(0) => break,
Ok(n) => filled += n,
Err(_) => return false,
}
}
head[..filled].starts_with(format!("{{\"schema_version\":{GRAPH_SCHEMA_VERSION},").as_bytes())
}
fn write_project_units(
root: &Path,
model: Option<&crate::project::ProjectModel>,
) -> io::Result<()> {
match model {
Some(m) => atomic_write_json(&project_units_path(root), &crate::project::graph_units(m)),
None => {
let path = project_units_path(root);
match fs::remove_file(&path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
}
}
}
pub fn rebuild_graph(
root: &Path,
graph_files: &[GraphFile],
fresh_fragments: &HashMap<String, AnyFragment>,
changed: bool,
model: Option<&crate::project::ProjectModel>,
) -> io::Result<RebuildOutcome> {
if !changed && graph_json_path(root).exists() && graph_schema_is_current(root) {
return Ok(RebuildOutcome::NotRebuilt);
}
let cache = read_fragments_cache(root);
let mut merged_cs: Vec<(String, Fragment)> = Vec::new();
let mut merged_ts: Vec<(String, extract::TsFragment)> = Vec::new();
let mut new_cache: OrderedMap<FragmentCacheEntry> = OrderedMap::new();
for f in graph_files {
let fragment = match cache.get(&f.rel) {
Some(entry) if entry.mtime == f.mtime => Some(entry.fragment.clone()),
_ => fresh_fragments.get(&f.rel).cloned(),
};
let Some(fragment) = fragment else { continue };
match &fragment {
AnyFragment::Cs(c) => merged_cs.push((f.rel.clone(), c.clone())),
AnyFragment::Ts(t) => merged_ts.push((f.rel.clone(), t.clone())),
}
new_cache.insert(
f.rel.clone(),
FragmentCacheEntry {
mtime: f.mtime,
fragment,
},
);
}
let graph = crate::resolve::resolve_graph_with_model(root, &merged_cs, &merged_ts, model);
write_graph(root, &graph)?;
write_project_units(root, model)?;
write_fragments_cache(root, &new_cache)?;
write_fragments_index(root, &new_cache)?;
remove_superseded_caches(root);
Ok(RebuildOutcome::Rebuilt(graph))
}