turbo_tasks_backend/database/
db_versioning.rs

1use std::{
2    env,
3    fs::{metadata, read_dir, remove_dir_all},
4    path::{Path, PathBuf},
5    time::Duration,
6};
7
8use anyhow::Result;
9
10/// Information gathered by `vergen_gitcl` in the top-level binary crate and passed down. This
11/// information must be computed in the top-level crate for cargo incremental compilation to work
12/// correctly.
13///
14/// See `crates/napi/build.rs` for details.
15pub struct GitVersionInfo<'a> {
16    /// Output of `git describe --match 'v[0-9]' --dirty`.
17    pub describe: &'a str,
18    /// Is the git repository dirty? Always forced to `false` when the `CI` environment variable is
19    /// set and non-empty.
20    pub dirty: bool,
21}
22
23/// Specifies many databases that have a different version than the current one are retained.
24/// For example if MAX_OTHER_DB_VERSIONS is 2, there can be at most 3 databases in the directory,
25/// the current one and two older/newer ones.
26const MAX_OTHER_DB_VERSIONS: usize = 2;
27
28pub fn handle_db_versioning(base_path: &Path, version_info: &GitVersionInfo) -> Result<PathBuf> {
29    if let Ok(version) = env::var("TURBO_ENGINE_VERSION") {
30        return Ok(base_path.join(version));
31    }
32    // Database versioning. Pass `TURBO_ENGINE_IGNORE_DIRTY` at runtime to ignore a
33    // dirty git repository. Pass `TURBO_ENGINE_DISABLE_VERSIONING` at runtime to disable
34    // versioning and always use the same database.
35    let ignore_dirty = env::var("TURBO_ENGINE_IGNORE_DIRTY").ok().is_some();
36    let disabled_versioning = env::var("TURBO_ENGINE_DISABLE_VERSIONING").ok().is_some();
37    let version = if disabled_versioning {
38        println!(
39            "WARNING: Persistent Caching versioning is disabled. Manual removal of the persistent \
40             caching database might be required."
41        );
42        Some("unversioned")
43    } else if !version_info.dirty {
44        Some(version_info.describe)
45    } else if ignore_dirty {
46        println!(
47            "WARNING: The git repository is dirty, but Persistent Caching is still enabled. \
48             Manual removal of the persistent caching database might be required."
49        );
50        Some(version_info.describe)
51    } else {
52        println!(
53            "WARNING: The git repository is dirty: Persistent Caching is disabled. Use \
54             TURBO_ENGINE_IGNORE_DIRTY=1 to ignore dirtyness of the repository."
55        );
56        None
57    };
58    let path;
59    if let Some(version) = version {
60        path = base_path.join(version);
61
62        // Remove old databases if needed
63        if let Ok(read_dir) = read_dir(base_path) {
64            let old_dbs = read_dir
65                .filter_map(|entry| {
66                    let entry = entry.ok()?;
67                    if !entry.file_type().ok()?.is_dir() {
68                        return None;
69                    }
70                    let name = entry.file_name();
71                    let name = name.to_string_lossy();
72                    if name == version {
73                        return None;
74                    }
75                    Some(entry.path())
76                })
77                .collect::<Vec<_>>();
78            if old_dbs.len() > MAX_OTHER_DB_VERSIONS {
79                let mut old_dbs = old_dbs
80                    .iter()
81                    .map(|p| {
82                        fn get_age(p: &Path) -> Result<Duration> {
83                            let m = metadata(p)?;
84                            Ok(m.accessed().or_else(|_| m.modified())?.elapsed()?)
85                        }
86                        (
87                            p,
88                            get_age(p).unwrap_or(Duration::from_secs(10 * 356 * 24 * 60 * 60)),
89                        )
90                    })
91                    .collect::<Vec<_>>();
92                old_dbs.sort_by_key(|(_, age)| *age);
93                for (p, _) in old_dbs.into_iter().skip(MAX_OTHER_DB_VERSIONS) {
94                    let _ = remove_dir_all(p);
95                }
96            }
97        }
98    } else {
99        path = base_path.join("temp");
100        let _ = remove_dir_all(&path);
101    }
102
103    Ok(path)
104}