char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somdeslop: split the task lib into modules7e18c89

main
4.3 KiB132 linesraw
1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use sha2::{Digest, Sha256};
5
6use crate::env::EnvLoader;
7use crate::glob::{expand_files, file_id};
8use crate::graph::{Graph, Task};
9use crate::{EnvMap, Error, Result};
10
11pub(crate) struct Fingerprinter {
12    graph: Graph,
13    root: PathBuf,
14    env: EnvLoader,
15    memo: HashMap<String, String>,
16    visiting: Vec<String>,
17}
18
19impl Fingerprinter {
20    pub(crate) fn new(graph: Graph, root: PathBuf) -> Self {
21        Fingerprinter {
22            env: EnvLoader::new(root.clone()),
23            graph,
24            root,
25            memo: HashMap::new(),
26            visiting: Vec::new(),
27        }
28    }
29
30    pub(crate) fn fingerprint(
31        &mut self,
32        name: &str,
33        args: &[String],
34        env: &EnvMap,
35    ) -> Result<String> {
36        if let Some(fingerprint) = self.memo.get(name) {
37            return Ok(fingerprint.clone());
38        }
39        if self.visiting.iter().any(|seen| seen == name) {
40            let mut cycle = self.visiting.clone();
41            cycle.push(name.to_owned());
42            return Err(Error::Cycle(cycle.join(" -> ")));
43        }
44        let task = self.graph.get(name)?.clone();
45        self.visiting.push(name.to_owned());
46        let result = self.fingerprint_task(name, &task, args, env);
47        self.visiting.pop();
48        let fingerprint = result?;
49        self.memo.insert(name.to_owned(), fingerprint.clone());
50        Ok(fingerprint)
51    }
52
53    fn fingerprint_task(
54        &mut self,
55        name: &str,
56        task: &Task,
57        args: &[String],
58        env: &EnvMap,
59    ) -> Result<String> {
60        fn field(hasher: &mut Sha256, value: &[u8]) {
61            hasher.update((value.len() as u64).to_be_bytes());
62            hasher.update(value);
63        }
64        let mut hasher = Sha256::new();
65        field(&mut hasher, b"avril-task-cache-v3");
66        field(&mut hasher, self.root.to_string_lossy().as_bytes());
67        field(&mut hasher, name.as_bytes());
68        field(&mut hasher, task.action.fingerprint_text().as_bytes());
69        field(
70            &mut hasher,
71            if task.parallel {
72                b"parallel".as_slice()
73            } else {
74                b"sequential".as_slice()
75            },
76        );
77        for arg in args {
78            field(&mut hasher, arg.as_bytes());
79        }
80        for var in &task.env {
81            field(&mut hasher, var.as_bytes());
82            match std::env::var(var) {
83                Ok(value) => {
84                    field(&mut hasher, b"set");
85                    field(&mut hasher, value.as_bytes());
86                }
87                Err(_) => {
88                    let normalized =
89                        avril_caps::env_name(var).unwrap_or_else(|_| var.as_str().into());
90                    match env.get(normalized.as_ref()) {
91                        Some(value) => {
92                            field(&mut hasher, b"file");
93                            field(&mut hasher, value.as_bytes());
94                        }
95                        None => field(&mut hasher, b"unset"),
96                    }
97                }
98            }
99        }
100        for dependency in &task.dependencies {
101            let dependency_task = self.graph.get(dependency)?.clone();
102            let dependency_env = self.env.effective(&dependency_task, env)?;
103            let fingerprint = self.fingerprint(dependency, &[], &dependency_env)?;
104            field(&mut hasher, dependency.as_bytes());
105            field(&mut hasher, fingerprint.as_bytes());
106        }
107        if let Some(patterns) = &task.files {
108            field(&mut hasher, b"cached");
109            for (relative, path) in expand_files(&self.root, name, patterns)? {
110                field(&mut hasher, relative.as_bytes());
111                let id = file_id(&path).map_err(|source| Error::Read {
112                    task: name.to_owned(),
113                    path: relative.clone(),
114                    source,
115                })?;
116                field(&mut hasher, id.as_bytes());
117            }
118        }
119        Ok(format!("{:x}", hasher.finalize()))
120    }
121}
122
123/// The cache fingerprint of `target` in `graph`, without running anything.
124pub fn graph_fingerprint(
125    graph: &Graph,
126    root: &Path,
127    target: &str,
128    args: &[String],
129) -> Result<String> {
130    let mut fingerprinter = Fingerprinter::new(graph.clone(), root.to_path_buf());
131    fingerprinter.fingerprint(target, args, &EnvMap::new())
132}