devscout_rs/graph/
rebuild.rs1use std::collections::HashMap;
2use std::fs;
3use std::io;
4use std::io::Read;
5use std::path::Path;
6
7use crate::extract;
8
9use super::artifact::{write_graph, Graph, GRAPH_SCHEMA_VERSION};
10use super::cache::{
11 read_fragments_cache, write_fragments_cache, write_fragments_index, FragmentCacheEntry,
12};
13use super::fragment_types::{AnyFragment, Fragment};
14use super::ordered::OrderedMap;
15use super::paths::{
16 atomic_write_json, graph_json_path, project_units_path, remove_superseded_caches,
17};
18
19#[derive(Debug, Clone)]
27pub struct GraphFile {
28 pub rel: String,
30 pub mtime: i64,
32}
33
34#[derive(Debug)]
35pub enum RebuildOutcome {
37 NotRebuilt,
39 Rebuilt(Graph),
41}
42
43pub(crate) fn graph_schema_is_current(root: &Path) -> bool {
51 let Ok(mut file) = fs::File::open(graph_json_path(root)) else {
52 return false;
53 };
54 let mut head = [0u8; 64];
55 let mut filled = 0;
56 while filled < head.len() {
57 match file.read(&mut head[filled..]) {
58 Ok(0) => break,
59 Ok(n) => filled += n,
60 Err(_) => return false,
61 }
62 }
63 head[..filled].starts_with(format!("{{\"schema_version\":{GRAPH_SCHEMA_VERSION},").as_bytes())
64}
65
66fn write_project_units(
71 root: &Path,
72 model: Option<&crate::project::ProjectModel>,
73) -> io::Result<()> {
74 match model {
75 Some(m) => atomic_write_json(&project_units_path(root), &crate::project::graph_units(m)),
76 None => {
77 let path = project_units_path(root);
78 match fs::remove_file(&path) {
79 Ok(()) => Ok(()),
80 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
81 Err(e) => Err(e),
82 }
83 }
84 }
85}
86
87pub fn rebuild_graph(
98 root: &Path,
99 graph_files: &[GraphFile],
100 fresh_fragments: &HashMap<String, AnyFragment>,
101 changed: bool,
102 model: Option<&crate::project::ProjectModel>,
103) -> io::Result<RebuildOutcome> {
104 if !changed && graph_json_path(root).exists() && graph_schema_is_current(root) {
105 return Ok(RebuildOutcome::NotRebuilt);
106 }
107
108 let cache = read_fragments_cache(root);
109 let mut merged_cs: Vec<(String, Fragment)> = Vec::new();
115 let mut merged_ts: Vec<(String, extract::TsFragment)> = Vec::new();
116 let mut new_cache: OrderedMap<FragmentCacheEntry> = OrderedMap::new();
117 for f in graph_files {
118 let fragment = match cache.get(&f.rel) {
119 Some(entry) if entry.mtime == f.mtime => Some(entry.fragment.clone()),
120 _ => fresh_fragments.get(&f.rel).cloned(),
121 };
122 let Some(fragment) = fragment else { continue };
123 match &fragment {
124 AnyFragment::Cs(c) => merged_cs.push((f.rel.clone(), c.clone())),
125 AnyFragment::Ts(t) => merged_ts.push((f.rel.clone(), t.clone())),
126 }
127 new_cache.insert(
128 f.rel.clone(),
129 FragmentCacheEntry {
130 mtime: f.mtime,
131 fragment,
132 },
133 );
134 }
135
136 let graph = crate::resolve::resolve_graph_with_model(root, &merged_cs, &merged_ts, model);
137 write_graph(root, &graph)?;
138 write_project_units(root, model)?;
139 write_fragments_cache(root, &new_cache)?;
140 write_fragments_index(root, &new_cache)?;
141 remove_superseded_caches(root);
142 Ok(RebuildOutcome::Rebuilt(graph))
143}