rucc_driver/cache.rs
1//! Where the sysroots and the other generated things are kept.
2//!
3//! Design: `spec/cross-compile/13-distribution.md` section 13.2, which names the directory and the
4//! variables that move it.
5//!
6//! This is the one place that answers the question, and it is here rather than in `rucc-sysroot`
7//! because answering it means reading the environment. `rucc-sysroot` reads nothing: that is what
8//! makes a link line a function of its arguments and what
9//! `spec/cross-compile/02-the-goal.md` claim 5 rests on. So the cache directory is resolved once,
10//! here, and handed down as a path, and the crate below stays a function of what it is given.
11//!
12//! Nothing here creates a directory or looks to see whether one is there. The answer is where a
13//! sysroot for a target would be, which is a question that has an answer before anything has been
14//! downloaded, and saying so is what lets a diagnostic name the directory that is missing.
15
16use std::path::PathBuf;
17
18/// The cache directory, from the environment.
19///
20/// `RUCC_CACHE_DIR` first, because somebody who set it meant it. Then the platform's own place for
21/// a cache that a user can delete without losing anything: `XDG_CACHE_HOME` or `~/.cache` on a
22/// Unix, `LOCALAPPDATA` on Windows, which is where a Windows program is expected to put this and
23/// not where `XDG_CACHE_HOME` would put it.
24///
25/// A temporary directory is the last answer rather than a failure. A machine with no home directory
26/// is a build container, and a build container that cannot link because nothing set `HOME` is worse
27/// than one that downloads a sysroot again on its next run.
28#[must_use]
29pub fn dir() -> PathBuf {
30 resolve(|name| std::env::var(name).ok())
31}
32
33/// The same answer from a function that says what the environment holds.
34///
35/// Split out so that the four cases are testable on one machine. A test that set the real
36/// environment would be a test that changed what the rest of the process sees, and these run in
37/// threads.
38fn resolve(var: impl Fn(&str) -> Option<String>) -> PathBuf {
39 // An empty value is a variable nobody set rather than a request to put the cache at the root of
40 // the filesystem, which is what `RUCC_CACHE_DIR=` in a makefile would otherwise mean.
41 let var = |name: &str| var(name).filter(|value| !value.is_empty());
42 if let Some(set) = var("RUCC_CACHE_DIR") {
43 return PathBuf::from(set);
44 }
45 if cfg!(windows) {
46 if let Some(local) = var("LOCALAPPDATA") {
47 return PathBuf::from(local).join("rucc").join("cache");
48 }
49 }
50 if let Some(xdg) = var("XDG_CACHE_HOME") {
51 return PathBuf::from(xdg).join("rucc");
52 }
53 if let Some(home) = var("HOME") {
54 return PathBuf::from(home).join(".cache").join("rucc");
55 }
56 std::env::temp_dir().join("rucc")
57}
58
59#[cfg(test)]
60mod tests {
61 use super::resolve;
62 use std::path::PathBuf;
63
64 /// An environment holding exactly these pairs.
65 fn env<'a>(pairs: &'a [(&'a str, &'a str)]) -> impl Fn(&str) -> Option<String> + 'a {
66 move |name| pairs.iter().find(|(key, _)| *key == name).map(|(_, value)| (*value).to_owned())
67 }
68
69 #[test]
70 fn the_variable_that_names_it_outright_wins() {
71 let dir = resolve(env(&[
72 ("RUCC_CACHE_DIR", "/build/cache"),
73 ("XDG_CACHE_HOME", "/home/a/.cache"),
74 ("HOME", "/home/a"),
75 ]));
76 assert_eq!(dir, PathBuf::from("/build/cache"));
77 }
78
79 #[test]
80 fn then_the_platforms_own_place_for_a_cache() {
81 let dir = resolve(env(&[("XDG_CACHE_HOME", "/home/a/.cache"), ("HOME", "/home/a")]));
82 assert_eq!(dir, PathBuf::from("/home/a/.cache/rucc"));
83 }
84
85 #[test]
86 #[cfg(windows)]
87 fn on_windows_it_is_where_a_windows_program_keeps_a_cache() {
88 // Not `~/.cache`, which is a Unix convention, and not the roaming profile either, because a
89 // cache that is copied between machines by a domain policy is a cache nobody wanted.
90 let dir = resolve(env(&[("LOCALAPPDATA", r"C:\Users\a\AppData\Local")]));
91 assert_eq!(dir, PathBuf::from(r"C:\Users\a\AppData\Local\rucc\cache"));
92 }
93
94 #[test]
95 fn then_the_home_directory() {
96 let dir = resolve(env(&[("HOME", "/home/a")]));
97 assert_eq!(dir, PathBuf::from("/home/a/.cache/rucc"));
98 }
99
100 #[test]
101 fn and_a_machine_with_nothing_set_still_gets_an_answer() {
102 // A build container with no `HOME`. It links, and it downloads again next time, which is
103 // the right way round for the two costs.
104 let dir = resolve(env(&[]));
105 assert!(dir.ends_with("rucc"), "{}", dir.display());
106 assert!(dir.is_absolute(), "{}", dir.display());
107 }
108
109 #[test]
110 fn an_empty_variable_is_not_an_answer() {
111 // `RUCC_CACHE_DIR=` is what a makefile that forwards a variable it was not given looks
112 // like, and taking it literally would put the cache at the root of the filesystem.
113 let dir = resolve(env(&[("RUCC_CACHE_DIR", ""), ("HOME", "/home/a")]));
114 assert_eq!(dir, PathBuf::from("/home/a/.cache/rucc"));
115 }
116}