skip_if/
strategies.rs

1use std::path::{Path, PathBuf};
2
3use tracing::*;
4
5use crate::Strategy;
6
7pub struct FileExists;
8
9impl<O> Strategy<O> for FileExists {
10    type E = ();
11    fn skip(&self, output: &Path, _args_hash: u64, _code_hash: u64) -> bool {
12        let exists = output.exists();
13        if exists {
14            warn!(?output, "Skipping as output exists");
15        }
16        exists
17    }
18}
19
20// See https://internals.rust-lang.org/t/14187
21pub fn append_ext(ext: impl AsRef<std::ffi::OsStr>, path: &Path) -> PathBuf {
22    let mut os_string: std::ffi::OsString = path.into();
23    os_string.push(".");
24    os_string.push(ext.as_ref());
25    os_string.into()
26}
27pub struct Markers<E> {
28    /// Use failure markers {output}.failure to skip previously failed calls.
29    pub failure_marker: bool,
30    pub retriable: Box<dyn Fn(&E) -> bool + Sync + Send>,
31    /// Use success markers {output}.success to rerun when arguments or code have changed.
32    /// This only makes sense with the `hashes` flag.
33    pub success_marker: bool,
34    /// Use code and arguments hashes for markers. If they changed, rerun failed and successful runs.
35    pub hashes: bool,
36    /// Assume the `output` attribute is a folder and store the markers in {output}/success and
37    /// {output}/failure.
38    pub folder: bool,
39}
40impl<E> Default for Markers<E> {
41    fn default() -> Self {
42        Self {
43            failure_marker: true,
44            retriable: Box::new(|_| true),
45            success_marker: true,
46            hashes: true,
47            folder: false,
48        }
49    }
50}
51
52impl<E> Markers<E> {
53    fn marker_path(&self, success: bool, output: &Path) -> PathBuf {
54        let name = if success { "success" } else { "failure" };
55        if self.folder {
56            output.join(name)
57        } else {
58            append_ext(name, output)
59        }
60    }
61    fn hashes_str(&self, args_hash: u64, code_hash: u64) -> String {
62        if self.hashes {
63            format!("{}\n{}", args_hash, code_hash)
64        } else {
65            Default::default()
66        }
67    }
68    pub fn folder(mut self) -> Self {
69        self.folder = true;
70        self
71    }
72    pub fn retriable(mut self, retriable: impl Fn(&E) -> bool + Sync + Send + 'static) -> Self {
73        self.retriable = Box::new(retriable);
74        self
75    }
76}
77
78impl<T, E> Strategy<Result<T, E>> for Markers<E> {
79    type E = std::io::Error;
80    fn skip(&self, output: &Path, args_hash: u64, code_hash: u64) -> bool {
81        let check_marker = |path: &Path| {
82            if let Ok(s) = std::fs::read_to_string(path) {
83                if s == self.hashes_str(args_hash, code_hash) {
84                    return true;
85                }
86            }
87            false
88        };
89        if self.failure_marker {
90            let marker = self.marker_path(false, output);
91            // Failure skipping
92            if check_marker(&marker) {
93                warn!(?marker, "Skipping due to failure marker");
94                return true;
95            }
96        }
97        if self.success_marker {
98            let marker = self.marker_path(true, output);
99            match (check_marker(&marker), output.exists()) {
100                (true, false) => {
101                    warn!(?marker, "Success marker exists, but not the output file");
102                    false
103                }
104                (true, true) => {
105                    warn!(?marker, "Skipping due to success marker");
106                    true
107                }
108                (false, _) => false,
109            }
110        } else {
111            // Otherwise we rely on the file existence
112            output.exists()
113        }
114    }
115    fn callback(
116        &self,
117        result: &Result<T, E>,
118        output: &Path,
119        args_hash: u64,
120        code_hash: u64,
121    ) -> Result<(), std::io::Error> {
122        let write_markers = |success: bool| {
123            // Write the failure or success marker
124            if self.folder {
125                std::fs::create_dir_all(output)?;
126            }
127            let path = self.marker_path(success, output);
128            debug!(
129                ?path,
130                "Writing {} marker",
131                if success { "success" } else { "failure" }
132            );
133            std::fs::write(path, self.hashes_str(args_hash, code_hash))?;
134            // Delete the other marker if it exists
135            let _ = std::fs::remove_file(self.marker_path(!success, output));
136            std::io::Result::Ok(())
137        };
138        match result {
139            Ok(_) if self.success_marker => {
140                write_markers(true)?;
141            }
142            Err(e) if self.failure_marker => {
143                if (self.retriable)(e) {
144                    debug!("Not writing a failure marker as the error is retriable");
145                } else {
146                    write_markers(false)?;
147                }
148            }
149            _ => {}
150        }
151        Ok(())
152    }
153}