ggen_core/codegen/
incremental_cache.rs1use crate::graph::Graph;
2use crate::manifest::{GenerationRule, GgenManifest};
3use ggen_utils::error::Result;
4use sha2::{Digest, Sha256};
5use std::collections::HashMap;
6use std::fs;
7use std::path::PathBuf;
8
9pub struct IncrementalCache {
10 cache_dir: PathBuf,
11 manifest_hash: String,
12 ontology_hash: String,
13 rule_hashes: HashMap<String, String>,
14 inference_state_hash: String,
15}
16
17pub struct CacheInvalidation {
18 pub manifest_changed: bool,
19 pub ontology_changed: bool,
20 pub changed_rules: Vec<String>,
21 pub inference_state_changed: bool,
22 pub should_rerun_all: bool,
23}
24
25impl IncrementalCache {
26 pub fn new(cache_dir: PathBuf) -> Self {
27 Self {
28 cache_dir,
29 manifest_hash: String::new(),
30 ontology_hash: String::new(),
31 rule_hashes: HashMap::new(),
32 inference_state_hash: String::new(),
33 }
34 }
35
36 pub fn load_cache_state(&mut self) -> Result<()> {
37 let manifest_hash_path = self.cache_dir.join("manifest.sha256");
38 let ontology_hash_path = self.cache_dir.join("ontology.sha256");
39 let rules_hash_path = self.cache_dir.join("rules.sha256");
40 let inference_hash_path = self.cache_dir.join("inference_state.sha256");
41
42 if manifest_hash_path.exists() {
43 self.manifest_hash = fs::read_to_string(&manifest_hash_path)?;
44 }
45
46 if ontology_hash_path.exists() {
47 self.ontology_hash = fs::read_to_string(&ontology_hash_path)?;
48 }
49
50 if rules_hash_path.exists() {
51 let rules_content = fs::read_to_string(&rules_hash_path)?;
52 for line in rules_content.lines() {
53 if let Some((name, hash)) = line.split_once('=') {
54 self.rule_hashes.insert(name.to_string(), hash.to_string());
55 }
56 }
57 }
58
59 if inference_hash_path.exists() {
60 self.inference_state_hash = fs::read_to_string(&inference_hash_path)?;
61 }
62
63 Ok(())
64 }
65
66 pub fn save_cache_state(
67 &self, manifest: &GgenManifest, ontology_content: &str, inference_graph: &Graph,
68 ) -> Result<()> {
69 fs::create_dir_all(&self.cache_dir)?;
70
71 let manifest_hash = Self::hash_manifest(manifest);
73 fs::write(self.cache_dir.join("manifest.sha256"), &manifest_hash)?;
74
75 let ontology_hash = Self::hash_string(ontology_content);
77 fs::write(self.cache_dir.join("ontology.sha256"), &ontology_hash)?;
78
79 let mut rules_content = String::new();
81 for rule in &manifest.generation.rules {
82 let rule_hash = Self::hash_generation_rule(rule);
83 rules_content.push_str(&format!("{}={}\n", rule.name, rule_hash));
84 }
85 fs::write(self.cache_dir.join("rules.sha256"), &rules_content)?;
86
87 let inference_hash = Self::hash_graph_state(inference_graph);
89 fs::write(
90 self.cache_dir.join("inference_state.sha256"),
91 &inference_hash,
92 )?;
93
94 Ok(())
95 }
96
97 pub fn check_invalidation(
98 &self, manifest: &GgenManifest, ontology_content: &str, inference_graph: &Graph,
99 ) -> CacheInvalidation {
100 let manifest_changed = self.manifest_hash != Self::hash_manifest(manifest);
101 let ontology_changed = self.ontology_hash != Self::hash_string(ontology_content);
102 let inference_state_changed =
103 self.inference_state_hash != Self::hash_graph_state(inference_graph);
104
105 let mut changed_rules = vec![];
106 for rule in &manifest.generation.rules {
107 let current_hash = Self::hash_generation_rule(rule);
108 let empty_hash = String::new();
109 let cached_hash = self.rule_hashes.get(&rule.name).unwrap_or(&empty_hash);
110
111 if current_hash != *cached_hash {
112 changed_rules.push(rule.name.clone());
113 }
114 }
115
116 let should_rerun_all = manifest_changed || ontology_changed || inference_state_changed;
117
118 CacheInvalidation {
119 manifest_changed,
120 ontology_changed,
121 changed_rules,
122 inference_state_changed,
123 should_rerun_all,
124 }
125 }
126
127 pub fn get_rules_to_rerun(
128 &self, manifest: &GgenManifest, invalidation: &CacheInvalidation,
129 rule_dependencies: &HashMap<String, Vec<String>>,
130 ) -> Vec<String> {
131 if invalidation.should_rerun_all {
132 return manifest
133 .generation
134 .rules
135 .iter()
136 .map(|r| r.name.clone())
137 .collect();
138 }
139
140 let mut to_rerun = invalidation.changed_rules.clone();
141 let mut visited = std::collections::HashSet::new();
142
143 for rule_name in invalidation.changed_rules.iter() {
145 Self::propagate_changes(rule_name, rule_dependencies, &mut to_rerun, &mut visited);
146 }
147
148 to_rerun
149 }
150
151 fn propagate_changes(
152 rule_name: &str, dependencies: &HashMap<String, Vec<String>>, to_rerun: &mut Vec<String>,
153 visited: &mut std::collections::HashSet<String>,
154 ) {
155 if visited.contains(rule_name) {
156 return;
157 }
158
159 visited.insert(rule_name.to_string());
160
161 for (dependent, deps) in dependencies {
163 if deps.contains(&rule_name.to_string()) && !to_rerun.contains(dependent) {
164 to_rerun.push(dependent.clone());
165 Self::propagate_changes(dependent, dependencies, to_rerun, visited);
166 }
167 }
168 }
169
170 fn hash_manifest(manifest: &GgenManifest) -> String {
171 let content = format!(
172 "{:?}{:?}{:?}{:?}",
173 manifest.project, manifest.ontology, manifest.inference, manifest.generation,
174 );
175 Self::hash_string(&content)
176 }
177
178 fn hash_generation_rule(rule: &GenerationRule) -> String {
179 let content = format!("{:?}", rule);
180 Self::hash_string(&content)
181 }
182
183 fn hash_graph_state(graph: &Graph) -> String {
184 let content = format!("{} triples", graph.len());
185 Self::hash_string(&content)
186 }
187
188 fn hash_string(content: &str) -> String {
189 let mut hasher = Sha256::new();
190 hasher.update(content.as_bytes());
191 format!("{:x}", hasher.finalize())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_hash_consistency() {
201 let content = "test content";
202 let hash1 = IncrementalCache::hash_string(content);
203 let hash2 = IncrementalCache::hash_string(content);
204 assert_eq!(hash1, hash2);
205 }
206
207 #[test]
208 fn test_hash_difference() {
209 let content1 = "test content 1";
210 let content2 = "test content 2";
211 let hash1 = IncrementalCache::hash_string(content1);
212 let hash2 = IncrementalCache::hash_string(content2);
213 assert_ne!(hash1, hash2);
214 }
215}