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_path))
29 .or_else(|| {
30 std::env::var_os(EnvVars::XDG_DATA_HOME)
31 .and_then(parse_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> {
88 let path = PathBuf::from(path);
89 if path.is_absolute() { Some(path) } else { None }
90}
91
92pub fn user_config_dir() -> Option<PathBuf> {
97 etcetera::choose_base_strategy()
98 .map(|dirs| dirs.config_dir())
99 .ok()
100}
101
102pub fn user_uv_config_dir() -> Option<PathBuf> {
103 user_config_dir().map(|mut path| {
104 path.push("uv");
105 path
106 })
107}
108
109#[cfg(not(windows))]
110fn locate_system_config_xdg(value: Option<&str>) -> Option<PathBuf> {
111 use std::path::Path;
114 let default = "/etc/xdg";
115 let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default);
116
117 for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) {
118 let uv_toml_path = Path::new(dir).join("uv").join("uv.toml");
119 if uv_toml_path.is_file() {
120 return Some(uv_toml_path);
121 }
122 }
123 None
124}
125
126#[cfg(windows)]
127fn locate_system_config_windows(system_drive: impl AsRef<Path>) -> Option<PathBuf> {
128 let candidate = system_drive
130 .as_ref()
131 .join("ProgramData")
132 .join("uv")
133 .join("uv.toml");
134 candidate.as_path().is_file().then_some(candidate)
135}
136
137pub fn system_config_file() -> Option<PathBuf> {
144 #[cfg(windows)]
145 {
146 env::var(EnvVars::SYSTEMDRIVE)
147 .ok()
148 .and_then(|system_drive| locate_system_config_windows(format!("{system_drive}\\")))
149 }
150
151 #[cfg(not(windows))]
152 {
153 if let Some(path) =
154 locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref())
155 {
156 return Some(path);
157 }
158
159 let candidate = Path::new("/etc/uv/uv.toml");
162 match candidate.try_exists() {
163 Ok(true) => Some(candidate.to_path_buf()),
164 Ok(false) => None,
165 Err(err) => {
166 tracing::warn!("Failed to query system configuration file: {err}");
167 None
168 }
169 }
170 }
171}
172
173#[cfg(test)]
174mod test {
175 #[cfg(windows)]
176 use crate::locate_system_config_windows;
177 #[cfg(not(windows))]
178 use crate::locate_system_config_xdg;
179
180 use assert_fs::fixture::FixtureError;
181 use assert_fs::prelude::*;
182 use indoc::indoc;
183
184 #[test]
185 #[cfg(not(windows))]
186 fn test_locate_system_config_xdg() -> Result<(), FixtureError> {
187 let context = assert_fs::TempDir::new()?;
189 context.child("uv").child("uv.toml").write_str(indoc! {
190 r#"
191 [pip]
192 index-url = "https://test.pypi.org/simple"
193 "#,
194 })?;
195
196 assert_eq!(locate_system_config_xdg(None), None);
198
199 assert_eq!(locate_system_config_xdg(Some("")), None);
201
202 assert_eq!(locate_system_config_xdg(Some(":")), None);
204
205 assert_eq!(
207 locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(),
208 context.child("uv").child("uv.toml").path()
209 );
210
211 let first = context.child("first");
213 let first_config = first.child("uv").child("uv.toml");
214 first_config.write_str("")?;
215
216 assert_eq!(
217 locate_system_config_xdg(Some(
218 format!("{}:{}", first.to_string_lossy(), context.to_string_lossy()).as_str()
219 ))
220 .unwrap(),
221 first_config.path()
222 );
223
224 Ok(())
225 }
226
227 #[test]
228 #[cfg(unix)]
229 fn test_locate_system_config_xdg_unix_permissions() -> Result<(), FixtureError> {
230 let context = assert_fs::TempDir::new()?;
231 let config = context.child("uv").child("uv.toml");
232 config.write_str("")?;
233 fs_err::set_permissions(
234 &context,
235 std::os::unix::fs::PermissionsExt::from_mode(0o000),
236 )
237 .unwrap();
238
239 assert_eq!(
240 locate_system_config_xdg(Some(context.to_str().unwrap())),
241 None
242 );
243
244 Ok(())
245 }
246
247 #[test]
248 #[cfg(windows)]
249 fn test_windows_config() -> Result<(), FixtureError> {
250 let context = assert_fs::TempDir::new()?;
252 context
253 .child("ProgramData")
254 .child("uv")
255 .child("uv.toml")
256 .write_str(indoc! { r#"
257 [pip]
258 index-url = "https://test.pypi.org/simple"
259 "#})?;
260
261 assert_eq!(
264 locate_system_config_windows(context.path()).unwrap(),
265 context
266 .child("ProgramData")
267 .child("uv")
268 .child("uv.toml")
269 .path()
270 );
271
272 let context = assert_fs::TempDir::new()?;
274 assert_eq!(locate_system_config_windows(context.path()), None);
275
276 Ok(())
277 }
278}