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_select! {
165 windows => {
166 env::var(EnvVars::SYSTEMDRIVE)
167 .ok()
168 .and_then(|system_drive| locate_system_config_windows(format!("{system_drive}\\")))
169 },
170 _ => {
171 if let Some(path) =
172 locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref())
173 {
174 return Some(path);
175 }
176
177 let candidate = Path::new("/etc/uv/uv.toml");
180 match candidate.try_exists() {
181 Ok(true) => Some(candidate.to_path_buf()),
182 Ok(false) => None,
183 Err(err) => {
184 tracing::warn!("Failed to query system configuration file: {err}");
185 None
186 }
187 }
188 },
189 }
190}
191
192#[cfg(test)]
193mod test {
194 #[cfg(windows)]
195 use crate::locate_system_config_windows;
196 #[cfg(not(windows))]
197 use crate::locate_system_config_xdg;
198
199 use assert_fs::fixture::FixtureError;
200 use assert_fs::prelude::*;
201 use indoc::indoc;
202
203 #[test]
204 #[cfg(not(windows))]
205 fn test_locate_system_config_xdg() -> Result<(), FixtureError> {
206 let context = assert_fs::TempDir::new()?;
208 context.child("uv").child("uv.toml").write_str(indoc! {
209 r#"
210 [pip]
211 index-url = "https://test.pypi.org/simple"
212 "#,
213 })?;
214
215 assert_eq!(locate_system_config_xdg(None), None);
217
218 assert_eq!(locate_system_config_xdg(Some("")), None);
220
221 assert_eq!(locate_system_config_xdg(Some(":")), None);
223
224 assert_eq!(
226 locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(),
227 context.child("uv").child("uv.toml").path()
228 );
229
230 let first = context.child("first");
232 let first_config = first.child("uv").child("uv.toml");
233 first_config.write_str("")?;
234
235 assert_eq!(
236 locate_system_config_xdg(Some(
237 format!("{}:{}", first.to_string_lossy(), context.to_string_lossy()).as_str()
238 ))
239 .unwrap(),
240 first_config.path()
241 );
242
243 Ok(())
244 }
245
246 #[test]
247 #[cfg(unix)]
248 fn test_locate_system_config_xdg_unix_permissions() -> Result<(), FixtureError> {
249 let context = assert_fs::TempDir::new()?;
250 let config = context.child("uv").child("uv.toml");
251 config.write_str("")?;
252 fs_err::set_permissions(
253 &context,
254 std::os::unix::fs::PermissionsExt::from_mode(0o000),
255 )
256 .unwrap();
257
258 assert_eq!(
259 locate_system_config_xdg(Some(context.to_str().unwrap())),
260 None
261 );
262
263 Ok(())
264 }
265
266 #[test]
267 #[cfg(windows)]
268 fn test_windows_config() -> Result<(), FixtureError> {
269 let context = assert_fs::TempDir::new()?;
271 context
272 .child("ProgramData")
273 .child("uv")
274 .child("uv.toml")
275 .write_str(indoc! { r#"
276 [pip]
277 index-url = "https://test.pypi.org/simple"
278 "#})?;
279
280 assert_eq!(
283 locate_system_config_windows(context.path()).unwrap(),
284 context
285 .child("ProgramData")
286 .child("uv")
287 .child("uv.toml")
288 .path()
289 );
290
291 let context = assert_fs::TempDir::new()?;
293 assert_eq!(locate_system_config_windows(context.path()), None);
294
295 Ok(())
296 }
297}