Skip to main content

ci_engine/
cache.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Explicit persistent cache-directory exports.
3
4use std::{
5    collections::BTreeMap,
6    path::{Path, PathBuf},
7};
8
9/// Prefix for every cache-directory environment variable.
10pub const CACHE_ENV_PREFIX: &str = "HCI_CACHE_";
11
12/// Prepared cache slots for one check.
13#[derive(Debug, Clone, Default)]
14pub struct PreparedCaches {
15    /// Environment exports.
16    pub env: BTreeMap<String, String>,
17    /// Directories successfully created.
18    pub dirs: Vec<PathBuf>,
19}
20
21/// Create declared cache slots under `cache_root`.
22/// A failed cache creation degrades to a cold build.
23#[must_use]
24pub fn prepare_caches(paths: &[String], cache_root: &Path) -> PreparedCaches {
25    let mut prepared = PreparedCaches::default();
26    let mut used = BTreeMap::<String, u32>::new();
27    for path in paths {
28        let base = slot_name(path);
29        let slot = match used.get_mut(&base) {
30            Some(count) => {
31                *count += 1;
32                format!("{base}_{count}")
33            }
34            None => {
35                used.insert(base.clone(), 0);
36                base
37            }
38        };
39        let directory = cache_root.join(&slot);
40        if std::fs::create_dir_all(&directory).is_err() {
41            continue;
42        }
43        prepared.env.insert(
44            format!("{CACHE_ENV_PREFIX}{slot}"),
45            directory.display().to_string(),
46        );
47        prepared.dirs.push(directory);
48    }
49    prepared
50}
51
52fn slot_name(path: &str) -> String {
53    let mut output = String::with_capacity(path.len());
54    let mut separated = true;
55    for character in path.chars() {
56        if character.is_ascii_alphanumeric() {
57            output.push(character.to_ascii_uppercase());
58            separated = false;
59        } else if !separated {
60            output.push('_');
61            separated = true;
62        }
63    }
64    while output.ends_with('_') {
65        output.pop();
66    }
67    if output.is_empty() {
68        "CACHE".to_string()
69    } else {
70        output
71    }
72}