1use std::{
2 env,
3 ffi::OsString,
4 path::{Path, PathBuf},
5};
6
7use etcetera::BaseStrategy;
8
9use uv_static::EnvVars;
10
11pub fn user_executable_directory(override_variable: Option<&'static str>) -> Option<PathBuf> {
25 override_variable
26 .and_then(std::env::var_os)
27 .and_then(parse_path)
28 .or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(parse_xdg_path))
29 .or_else(|| {
30 std::env::var_os(EnvVars::XDG_DATA_HOME)
31 .and_then(parse_xdg_path)
32 .map(|path| path.join("../bin"))
33 })
34 .or_else(|| {
35 let home_dir = etcetera::home_dir().ok();
36 home_dir.map(|path| path.join(".local").join("bin"))
37 })
38}
39
40pub fn user_cache_dir() -> Option<PathBuf> {
44 etcetera::base_strategy::choose_base_strategy()
45 .ok()
46 .map(|dirs| dirs.cache_dir().join("uv"))
47}
48
49pub fn legacy_user_cache_dir() -> Option<PathBuf> {
54 etcetera::base_strategy::choose_native_strategy()
55 .ok()
56 .map(|dirs| dirs.cache_dir().join("uv"))
57 .map(|dir| {
58 if cfg!(windows) {
59 dir.join("cache")
60 } else {
61 dir
62 }
63 })
64}
65
66pub fn user_state_dir() -> Option<PathBuf> {
70 etcetera::base_strategy::choose_base_strategy()
71 .ok()
72 .map(|dirs| dirs.data_dir().join("uv"))
73}
74
75pub fn legacy_user_state_dir() -> Option<PathBuf> {
80 etcetera::base_strategy::choose_native_strategy()
81 .ok()
82 .map(|dirs| dirs.data_dir().join("uv"))
83 .map(|dir| if cfg!(windows) { dir.join("data") } else { dir })
84}
85
86fn parse_path(path: OsString) -> Option<PathBuf> {
91 if path.is_empty() {
92 None
93 } else {
94 Some(PathBuf::from(path))
95 }
96}
97
98fn parse_xdg_path(path: OsString) -> Option<PathBuf> {
108 let path = PathBuf::from(path);
109 if path.is_absolute() { Some(path) } else { None }
110}
111
112pub fn user_config_dir() -> Option<PathBuf> {
117 etcetera::choose_base_strategy()
118 .map(|dirs| dirs.config_dir())
119 .ok()
120}
121
122pub fn user_uv_config_dir() -> Option<PathBuf> {
123 user_config_dir().map(|mut path| {
124 path.push("uv");
125 path
126 })
127}
128
129#[cfg(not(windows))]
130fn locate_system_config_xdg(value: Option<&str>) -> Option<PathBuf> {
131 use std::path::Path;
134 let default = "/etc/xdg";
135 let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default);
136
137 for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) {
138 let uv_toml_path = Path::new(dir).join("uv").join("uv.toml");
139 if uv_toml_path.is_file() {
140 return Some(uv_toml_path);
141 }
142 }
143 None
144}
145
146#[cfg(windows)]
147fn locate_system_config_windows(system_drive: impl AsRef<Path>) -> Option<PathBuf> {
148 let candidate = system_drive
150 .as_ref()
151 .join("ProgramData")
152 .join("uv")
153 .join("uv.toml");
154 candidate.as_path().is_file().then_some(candidate)
155}
156
157pub fn system_config_file() -> Option<PathBuf> {
164 #[cfg(windows)]
165 {
166 env::var(EnvVars::SYSTEMDRIVE)
167 .ok()
168 .and_then(|system_drive| locate_system_config_windows(format!("{system_drive}\\")))
169 }
170
171 #[cfg(not(windows))]
172 {
173 if let Some(path) =
174 locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref())
175 {
176 return Some(path);
177 }
178
179 let candidate = Path::new("/etc/uv/uv.toml");
182 match candidate.try_exists() {
183 Ok(true) => Some(candidate.to_path_buf()),
184 Ok(false) => None,
185 Err(err) => {
186 tracing::warn!("Failed to query system configuration file: {err}");
187 None
188 }
189 }
190 }
191}
192
193#[cfg(test)]
194mod test {
195 #[cfg(windows)]
196 use crate::locate_system_config_windows;
197 #[cfg(not(windows))]
198 use crate::locate_system_config_xdg;
199
200 use assert_fs::fixture::FixtureError;
201 use assert_fs::prelude::*;
202 use indoc::indoc;
203
204 #[test]
205 #[cfg(not(windows))]
206 fn test_locate_system_config_xdg() -> Result<(), FixtureError> {
207 let context = assert_fs::TempDir::new()?;
209 context.child("uv").child("uv.toml").write_str(indoc! {
210 r#"
211 [pip]
212 index-url = "https://test.pypi.org/simple"
213 "#,
214 })?;
215
216 assert_eq!(locate_system_config_xdg(None), None);
218
219 assert_eq!(locate_system_config_xdg(Some("")), None);
221
222 assert_eq!(locate_system_config_xdg(Some(":")), None);
224
225 assert_eq!(
227 locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(),
228 context.child("uv").child("uv.toml").path()
229 );
230
231 let first = context.child("first");
233 let first_config = first.child("uv").child("uv.toml");
234 first_config.write_str("")?;
235
236 assert_eq!(
237 locate_system_config_xdg(Some(
238 format!("{}:{}", first.to_string_lossy(), context.to_string_lossy()).as_str()
239 ))
240 .unwrap(),
241 first_config.path()
242 );
243
244 Ok(())
245 }
246
247 #[test]
248 #[cfg(unix)]
249 fn test_locate_system_config_xdg_unix_permissions() -> Result<(), FixtureError> {
250 let context = assert_fs::TempDir::new()?;
251 let config = context.child("uv").child("uv.toml");
252 config.write_str("")?;
253 fs_err::set_permissions(
254 &context,
255 std::os::unix::fs::PermissionsExt::from_mode(0o000),
256 )
257 .unwrap();
258
259 assert_eq!(
260 locate_system_config_xdg(Some(context.to_str().unwrap())),
261 None
262 );
263
264 Ok(())
265 }
266
267 #[test]
268 #[cfg(windows)]
269 fn test_windows_config() -> Result<(), FixtureError> {
270 let context = assert_fs::TempDir::new()?;
272 context
273 .child("ProgramData")
274 .child("uv")
275 .child("uv.toml")
276 .write_str(indoc! { r#"
277 [pip]
278 index-url = "https://test.pypi.org/simple"
279 "#})?;
280
281 assert_eq!(
284 locate_system_config_windows(context.path()).unwrap(),
285 context
286 .child("ProgramData")
287 .child("uv")
288 .child("uv.toml")
289 .path()
290 );
291
292 let context = assert_fs::TempDir::new()?;
294 assert_eq!(locate_system_config_windows(context.path()), None);
295
296 Ok(())
297 }
298}