Skip to main content

huff_neo_core/
cache.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use huff_neo_utils::file::file_source::FileSource;
4use huff_neo_utils::file::full_file_source::OutputLocation;
5use huff_neo_utils::prelude::Artifact;
6use walkdir::WalkDir;
7
8/// Parallelized Artifact Cachcing
9pub fn get_cached_artifacts(
10    files: &[Arc<FileSource>],
11    out: &OutputLocation,
12    constructor_args: &[String],
13    constant_overrides: &BTreeMap<String, String>,
14) -> Option<Vec<Arc<Artifact>>> {
15    // Check if the file artifacts are already generated the the default "./artifacts/" directory or
16    // the specified output dir
17    let artifacts: Vec<(Arc<FileSource>, Artifact)> = resolve_existing_artifacts(files, out, constructor_args, constant_overrides)?;
18
19    // Return the artifacts if cached
20    Some(artifacts.into_iter().map(|(_, artifact)| Arc::new(artifact)).collect())
21}
22
23/// Attempt to grab the artifacts
24pub fn resolve_existing_artifacts(
25    files: &[Arc<FileSource>],
26    output: &OutputLocation,
27    constructor_args: &[String],
28    constant_overrides: &BTreeMap<String, String>,
29) -> Option<Vec<(Arc<FileSource>, Artifact)>> {
30    let mut artifacts: Vec<(Arc<FileSource>, Artifact)> = Vec::new();
31
32    // Transform file sources into a hashmap of path to file source
33    let mut file_sources: std::collections::HashMap<String, Arc<FileSource>> =
34        files.iter().map(|f| (f.path.clone().to_lowercase(), Arc::clone(f))).collect();
35
36    // If outputdir is not specified, use the default "./artifacts/" directory
37    let output_dir = if !output.0.is_empty() { &*output.0 } else { "./artifacts" };
38
39    // For each file, check if the artifact file exists at the location
40    tracing::debug!(target: "core", "Traversing output directory {}", output_dir);
41    // Only JSON files can be artifacts; the output directory may hold other files too
42    let artifact_files = WalkDir::new(output_dir)
43        .into_iter()
44        .filter_map(Result::ok)
45        .filter(|e| e.file_type().is_file() && e.path().extension().is_some_and(|ext| ext == "json"));
46    for entry in artifact_files {
47        // Are we expecting this file to be compiled
48        let formatted_path = entry.path().with_extension("").display().to_string().replace(output_dir, ".").to_lowercase();
49        let expected = file_sources.remove(&formatted_path);
50
51        // Try to read the file into an artifact
52        match serde_json::from_str::<Artifact>(&std::fs::read_to_string(entry.path()).unwrap()) {
53            Ok(artifact) => {
54                // If we expected compilation, the sources must match
55                match expected {
56                    Some(expected_fs) => {
57                        if artifact.constructor_args != constructor_args {
58                            tracing::warn!(target: "core", "Mismatched Constructor Args for Cached Artifact \"{}\"", artifact.file.path);
59                            return None;
60                        }
61                        if &artifact.constant_overrides != constant_overrides {
62                            tracing::warn!(target: "core", "Mismatched Constant Overrides for Cached Artifact \"{}\"", artifact.file.path);
63                            return None;
64                        }
65                        if artifact.file.source != expected_fs.source {
66                            tracing::warn!(target: "core", "Cache Resolution Failed: \"{}\" Artifact Outdated", artifact.file.path);
67                            return None;
68                        } else {
69                            artifacts.push((expected_fs, artifact));
70                        }
71                    }
72                    None => {
73                        tracing::warn!(target: "core", "Cache Resolution Found Unexpected Artifact: \"{}\"", artifact.file.path)
74                    }
75                }
76            }
77            Err(e) => {
78                // The output directory may hold other JSON files, which are only an error when an
79                // artifact was expected
80                tracing::debug!(target: "core", "Not an artifact file: {}", e);
81                if expected.is_some() {
82                    tracing::error!(target: "core", "Expected artifact file to be compiled: {}", entry.path().display());
83                    return None;
84                }
85            }
86        }
87    }
88
89    match file_sources.is_empty() {
90        true => Some(artifacts),
91        false => {
92            tracing::warn!(target: "core", "Cache Resolution Failed: Missing Artifact Files");
93            None
94        }
95    }
96}