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 semantic_layer: Option<&crate::semantic::SemanticLayer>,
104) -> io::Result<RebuildOutcome> {
105 if !changed && graph_json_path(root).exists() && graph_schema_is_current(root) {
106 return Ok(RebuildOutcome::NotRebuilt);
107 }
108
109 let cache = read_fragments_cache(root);
110 let mut merged_cs: Vec<(String, Fragment)> = Vec::new();
116 let mut merged_ts: Vec<(String, extract::TsFragment)> = Vec::new();
117 let mut new_cache: OrderedMap<FragmentCacheEntry> = OrderedMap::new();
118 for f in graph_files {
119 let fragment = match cache.get(&f.rel) {
120 Some(entry) if entry.mtime == f.mtime => Some(entry.fragment.clone()),
121 _ => fresh_fragments.get(&f.rel).cloned(),
122 };
123 let Some(fragment) = fragment else { continue };
124 match &fragment {
125 AnyFragment::Cs(c) => merged_cs.push((f.rel.clone(), c.clone())),
126 AnyFragment::Ts(t) => merged_ts.push((f.rel.clone(), t.clone())),
127 }
128 new_cache.insert(
129 f.rel.clone(),
130 FragmentCacheEntry {
131 mtime: f.mtime,
132 fragment,
133 },
134 );
135 }
136
137 let graph = crate::resolve::resolve_graph_with_model(
138 root,
139 &merged_cs,
140 &merged_ts,
141 model,
142 semantic_layer,
143 );
144 write_graph(root, &graph)?;
145 write_project_units(root, model)?;
146 write_fragments_cache(root, &new_cache)?;
147 write_fragments_index(root, &new_cache)?;
148 remove_superseded_caches(root);
149 Ok(RebuildOutcome::Rebuilt(graph))
150}