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::lane::LaneStats;
use super::ordered::OrderedMap;
use super::paths::{
atomic_write_json, graph_json_path, project_units_path, remove_superseded_caches,
rust_crates_path,
};
#[derive(Debug, Clone)]
pub struct GraphFile {
pub rel: String,
pub mtime: i64,
}
#[derive(Debug)]
pub enum RebuildOutcome {
NotRebuilt,
Rebuilt(Graph),
}
impl RebuildOutcome {
pub fn lane(&self) -> Option<&LaneStats> {
match self {
RebuildOutcome::Rebuilt(graph) => graph.stats.lane.as_ref(),
RebuildOutcome::NotRebuilt => None,
}
}
}
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),
}
}
}
}
fn write_rust_crates(
root: &Path,
crates: Option<&crate::rsgraph::crates::RustCrates>,
) -> io::Result<()> {
match crates {
Some(c) => atomic_write_json(&rust_crates_path(root), c),
None => match fs::remove_file(rust_crates_path(root)) {
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>,
lane: Option<&crate::semantic::LaneDecision>,
) -> io::Result<RebuildOutcome> {
rebuild_graph_with_crates(
root,
graph_files,
fresh_fragments,
changed,
model,
lane,
None,
)
}
pub fn rebuild_graph_with_crates(
root: &Path,
graph_files: &[GraphFile],
fresh_fragments: &HashMap<String, AnyFragment>,
changed: bool,
model: Option<&crate::project::ProjectModel>,
lane: Option<&crate::semantic::LaneDecision>,
crates: Option<&crate::rsgraph::crates::RustCrates>,
) -> 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 merged_rs: Vec<(String, extract::RsFragment)> = 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())),
AnyFragment::Rs(r) => merged_rs.push((f.rel.clone(), r.clone())),
}
new_cache.insert(
f.rel.clone(),
FragmentCacheEntry {
mtime: f.mtime,
fragment,
},
);
}
let mut graph = crate::resolve::resolve_graph_with_model(
root,
&merged_cs,
&merged_ts,
model,
lane.and_then(crate::semantic::LaneDecision::layer),
);
crate::semantic::record_lane(lane, &mut graph.stats);
crate::rsgraph::append_rust(&mut graph, &merged_rs);
crate::rsgraph::append_rust_edges(&mut graph, &merged_rs, crates);
write_graph(root, &graph)?;
write_project_units(root, model)?;
write_rust_crates(root, crates)?;
write_fragments_cache(root, &new_cache)?;
write_fragments_index(root, &new_cache)?;
remove_superseded_caches(root);
Ok(RebuildOutcome::Rebuilt(graph))
}