1use std::collections::BTreeMap;
14use std::path::{Path, PathBuf};
15
16pub fn downstream_root(cache_dir: &Path) -> PathBuf {
18 cache_dir.join("pkg")
19}
20
21pub fn cache_env(
24 cache_dir: &Path,
25 getenv: impl Fn(&str) -> Option<String>,
26) -> BTreeMap<String, String> {
27 let root = downstream_root(cache_dir);
28 let mut env = BTreeMap::new();
29
30 let mut set_if_unset = |key: &str, path: PathBuf| {
31 if variable_is_available(key, &getenv) {
32 env.insert(key.to_string(), path.display().to_string());
33 }
34 };
35
36 set_if_unset("npm_config_cache", root.join("npm"));
39 set_if_unset("PIP_CACHE_DIR", root.join("pip"));
41 set_if_unset("GOMODCACHE", root.join("go-mod"));
43 set_if_unset("GOCACHE", root.join("go-build"));
44 set_if_unset("CARGO_HOME", root.join("cargo"));
46 set_if_unset("GRADLE_USER_HOME", root.join("gradle"));
48
49 env
50}
51
52pub fn manager_env(
59 cache_dir: &Path,
60 mappings: &[(&str, &str)],
61 getenv: impl Fn(&str) -> Option<String>,
62) -> BTreeMap<String, String> {
63 let root = downstream_root(cache_dir);
64 mappings
65 .iter()
66 .filter(|(key, _)| variable_is_available(key, &getenv))
67 .map(|(key, directory)| {
68 (
69 (*key).to_string(),
70 root.join(directory).display().to_string(),
71 )
72 })
73 .collect()
74}
75
76pub fn manager_exec_env(cache_dir: &Path, mappings: &[(&str, &str)]) -> BTreeMap<String, String> {
78 manager_env(cache_dir, mappings, |key| std::env::var(key).ok())
79}
80
81fn variable_is_available(key: &str, getenv: &impl Fn(&str) -> Option<String>) -> bool {
82 getenv(&format!("OSDK_ORIG_{key}_SET")).is_some() || getenv(key).is_none()
83}
84
85pub fn describe(cache_dir: &Path) -> Vec<(String, String)> {
87 let mut env = cache_env(cache_dir, |_| None);
88 for (key, value) in manager_env(
89 cache_dir,
90 &[
91 ("npm_config_cache", "npm"),
92 ("PNPM_HOME", "pnpm"),
93 ("npm_config_store_dir", "pnpm-store"),
94 ("pnpm_config_store_dir", "pnpm-store"),
95 ("YARN_CACHE_FOLDER", "yarn-classic"),
96 ("YARN_GLOBAL_FOLDER", "yarn"),
97 ("BUN_INSTALL_CACHE_DIR", "bun"),
98 ("DENO_DIR", "deno"),
99 ],
100 |_| None,
101 ) {
102 env.insert(key, value);
103 }
104 env.into_iter().collect()
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use std::collections::HashMap;
111
112 #[test]
113 fn maps_known_managers() {
114 let cache = PathBuf::from("/x/cache");
115 let env = cache_env(&cache, |_| None);
116 assert_eq!(
117 PathBuf::from(env.get("PIP_CACHE_DIR").unwrap()),
118 cache.join("pkg/pip")
119 );
120 assert_eq!(
121 PathBuf::from(env.get("GOMODCACHE").unwrap()),
122 cache.join("pkg/go-mod")
123 );
124 assert_eq!(
125 PathBuf::from(env.get("npm_config_cache").unwrap()),
126 cache.join("pkg/npm")
127 );
128
129 let npm = manager_env(&cache, &[("npm_config_cache", "npm")], |_| None);
130 assert_eq!(
131 PathBuf::from(npm.get("npm_config_cache").unwrap()),
132 cache.join("pkg/npm")
133 );
134 }
135
136 #[test]
137 fn respects_user_set_vars() {
138 let cache = PathBuf::from("/x/cache");
139 let mut user = HashMap::new();
140 user.insert("PIP_CACHE_DIR".to_string(), "/custom/pip".to_string());
141 let env = cache_env(&cache, |k| user.get(k).cloned());
142 assert!(!env.contains_key("PIP_CACHE_DIR"));
144 assert!(env.contains_key("GOMODCACHE"));
146
147 let managed = cache_env(&cache, |key| match key {
148 "PIP_CACHE_DIR" => Some("/old/osdk/pkg/pip".into()),
149 "OSDK_ORIG_PIP_CACHE_DIR_SET" => Some("1".into()),
150 _ => None,
151 });
152 assert_eq!(
153 PathBuf::from(managed.get("PIP_CACHE_DIR").unwrap()),
154 cache.join("pkg/pip")
155 );
156 }
157
158 #[test]
159 fn manager_env_preserves_user_values_but_refreshes_hook_managed_values() {
160 let cache = PathBuf::from("/x/cache");
161 let mappings = &[("npm_config_cache", "npm")];
162 let user = manager_env(&cache, mappings, |key| {
163 (key == "npm_config_cache").then(|| "/custom/npm".into())
164 });
165 assert!(user.is_empty());
166
167 let managed = manager_env(&cache, mappings, |key| match key {
168 "npm_config_cache" => Some("/old/osdk/pkg/npm".into()),
169 "OSDK_ORIG_npm_config_cache_SET" => Some("1".into()),
170 _ => None,
171 });
172 assert_eq!(
173 PathBuf::from(managed.get("npm_config_cache").unwrap()),
174 cache.join("pkg/npm")
175 );
176 }
177
178 #[test]
179 fn describe_includes_bun_and_deno_manager_caches() {
180 let cache = PathBuf::from("/x/cache");
181 let described = describe(&cache).into_iter().collect::<BTreeMap<_, _>>();
182 assert_eq!(
183 PathBuf::from(described.get("BUN_INSTALL_CACHE_DIR").unwrap()),
184 cache.join("pkg/bun")
185 );
186 assert_eq!(
187 PathBuf::from(described.get("DENO_DIR").unwrap()),
188 cache.join("pkg/deno")
189 );
190 }
191}