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, in
33 /// [`Database::open_at`](crate::persistence::Database::open_at).
34 pub fn root(&self) -> std::path::PathBuf {
35 match self {
36 // A daemon or test harness whose cwd was deleted has no current
37 // directory at all.
38 Self::Local => std::env::current_dir().unwrap_or_else(|err| {
39 log::warn!("cubecl cache: no current directory ({err}); using the user cache");
40 user_cache_dir()
41 }),
42 Self::Target => {
43 let start = match std::env::current_dir() {
44 Ok(dir) => dir,
45 // Same condition `Local` reports: a daemon or test harness
46 // whose cwd was deleted has no current directory to search
47 // from.
48 Err(err) => {
49 log::warn!(
50 "cubecl cache: no current directory ({err}); using the user cache"
51 );
52 return user_cache_dir();
53 }
54 };
55
56 // The *outermost* Cargo.toml, not the first one found walking
57 // up: a workspace member has its own manifest, but `target/`
58 // lives at the workspace root, so stopping at the innermost
59 // manifest would scatter caches into `member/target`.
60 let mut root = None;
61 let mut dir = start.as_path();
62 loop {
63 if let Ok(true) = std::fs::exists(dir.join("Cargo.toml")) {
64 root = Some(dir);
65 }
66 match dir.parent() {
67 Some(parent) => dir = parent,
68 None => break,
69 }
70 }
71
72 match root {
73 Some(root) => root.join("target").join("environment"),
74 // No Cargo.toml anywhere above cwd — this is a bundled or
75 // installed application (Tauri, GUI app, CLI installed via
76 // cargo install, etc.) running outside a workspace. Joining
77 // "target" onto the original cwd became `/target` when cwd
78 // was `/`, which fails on most platforms with EROFS and
79 // cascaded a directory failure into the whole autotune
80 // pipeline. Use the platform-appropriate user cache
81 // directory instead.
82 None => user_cache_dir(),
83 }
84 }
85 // The cache directory, not the configuration directory: this is
86 // regenerable data, and XDG says so. It also matches the `Target`
87 // fallback right above, which already used `cache_dir`.
88 Self::Global => user_cache_dir(),
89 Self::Directory(path_buf) => path_buf.clone(),
90 }
91 }
92}
93
94/// The user cache directory, or a temporary one when the platform can't name
95/// it — a systemd unit with no `HOME`, a distroless container.
96fn user_cache_dir() -> std::path::PathBuf {
97 match etcetera::choose_base_strategy() {
98 Ok(strategy) => strategy.cache_dir().join("cubecl"),
99 Err(err) => {
100 log::warn!(
101 "cubecl cache: no user cache directory ({err}); \
102 falling back to the temporary directory"
103 );
104 std::env::temp_dir().join("cubecl")
105 }
106 }
107}