cubecl_environment/persistence/root.rs
1use etcetera::BaseStrategy;
2
3/// Cache location options.
4#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
5pub enum CacheConfig {
6 /// Stores cache in the current working directory.
7 #[serde(rename = "local")]
8 Local,
9
10 /// Stores cache in the project's `target/environment` directory
11 /// (default).
12 #[default]
13 #[serde(rename = "target")]
14 Target,
15
16 /// Stores cache in the system's user cache directory.
17 #[serde(rename = "global")]
18 Global,
19
20 /// Stores cache under a user-specified directory. The environment
21 /// database file is placed inside it, not at this path.
22 #[serde(rename = "directory")]
23 Directory(std::path::PathBuf),
24}
25
26impl CacheConfig {
27 /// Returns the root directory for the cache.
28 ///
29 /// Every arm degrades rather than fails: none of these look-ups is under
30 /// the application's control, and a cache root that can't be resolved must
31 /// cost a recompute, not abort the process. A root that turns out to be
32 /// unwritable is handled one level down, when the database is opened.
33 pub fn root(&self) -> std::path::PathBuf {
34 match self {
35 // A daemon or test harness whose cwd was deleted has no current
36 // directory at all.
37 Self::Local => std::env::current_dir().unwrap_or_else(|err| {
38 log::warn!("cubecl cache: no current directory ({err}); using the user cache");
39 user_cache_dir()
40 }),
41 Self::Target => {
42 let start = match std::env::current_dir() {
43 Ok(dir) => dir,
44 // Same condition `Local` reports: a daemon or test harness
45 // whose cwd was deleted has no current directory to search
46 // from.
47 Err(err) => {
48 log::warn!(
49 "cubecl cache: no current directory ({err}); using the user cache"
50 );
51 return user_cache_dir();
52 }
53 };
54
55 // The *outermost* Cargo.toml, not the first one found walking
56 // up: a workspace member has its own manifest, but `target/`
57 // lives at the workspace root, so stopping at the innermost
58 // manifest would scatter caches into `member/target`.
59 let mut root = None;
60 let mut dir = start.as_path();
61 loop {
62 if let Ok(true) = std::fs::exists(dir.join("Cargo.toml")) {
63 root = Some(dir);
64 }
65 match dir.parent() {
66 Some(parent) => dir = parent,
67 None => break,
68 }
69 }
70
71 match root {
72 Some(root) => root.join("target").join("environment"),
73 // No Cargo.toml anywhere above cwd — this is a bundled or
74 // installed application (Tauri, GUI app, CLI installed via
75 // cargo install, etc.) running outside a workspace. Joining
76 // "target" onto the original cwd became `/target` when cwd
77 // was `/`, which fails on most platforms with EROFS and
78 // cascaded a directory failure into the whole autotune
79 // pipeline. Use the platform-appropriate user cache
80 // directory instead.
81 None => user_cache_dir(),
82 }
83 }
84 // The cache directory, not the configuration directory: this is
85 // regenerable data, and XDG says so. It also matches the `Target`
86 // fallback right above, which already used `cache_dir`.
87 Self::Global => user_cache_dir(),
88 Self::Directory(path_buf) => path_buf.clone(),
89 }
90 }
91}
92
93/// The user cache directory, or a temporary one when the platform can't name
94/// it — a systemd unit with no `HOME`, a distroless container.
95fn user_cache_dir() -> std::path::PathBuf {
96 match etcetera::choose_base_strategy() {
97 Ok(strategy) => strategy.cache_dir().join("cubecl"),
98 Err(err) => {
99 log::warn!(
100 "cubecl cache: no user cache directory ({err}); \
101 falling back to the temporary directory"
102 );
103 std::env::temp_dir().join("cubecl")
104 }
105 }
106}