From 7f33681e61b9f72218c9c11bca114bed243f2260 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Mon, 30 Sep 2024 16:54:37 -0700 Subject: [PATCH 01/21] Add system level uv.toml config. --- crates/uv-settings/src/lib.rs | 40 +++++++++++++++++++++++++++++++++-- crates/uv/src/lib.rs | 8 ++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 2ee366bf92f0a..833a141af41e2 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -36,7 +36,7 @@ impl Deref for FilesystemOptions { impl FilesystemOptions { /// Load the user [`FilesystemOptions`]. pub fn user() -> Result, Error> { - let Some(dir) = config_dir() else { + let Some(dir) = user_config_dir() else { return Ok(None); }; let root = dir.join("uv"); @@ -61,6 +61,25 @@ impl FilesystemOptions { } } + pub fn system() -> Result, Error> { + let dir = system_config_dir(); + let root = dir.join("uv"); + let file = root.join("uv.toml"); + + debug!( + "Searching for system configuration in: `{}`", + file.display() + ); + match read_file(&file) { + Ok(options) => { + debug!("Found system configuration in: `{}`", file.display()); + Ok(Some(Self(options))) + } + Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + } + } + /// Find the [`FilesystemOptions`] for the given path. /// /// The search starts at the given path and goes up the directory tree until a `uv.toml` file or @@ -171,7 +190,7 @@ impl From for FilesystemOptions { /// This is similar to the `config_dir()` returned by the `dirs` crate, but it uses the /// `XDG_CONFIG_HOME` environment variable on both Linux _and_ macOS, rather than the /// `Application Support` directory on macOS. -fn config_dir() -> Option { +fn user_config_dir() -> Option { // On Windows, use, e.g., C:\Users\Alice\AppData\Roaming #[cfg(windows)] { @@ -187,6 +206,23 @@ fn config_dir() -> Option { } } +/// Returns the path to the system configuration directory. +/// +/// Right now this defaults to "/etc/" in *nix systems and "C:\ProgramData" on Windows. +fn system_config_dir() -> PathBuf { + // On Windows, use, e.g., C:\ProgramData + #[cfg(windows)] + { + PathBuf::from("C:\\ProgramData") + } + + // On Linux and macOS, use /etc/uv/uv.toml. + #[cfg(not(windows))] + { + PathBuf::from("/etc") + } +} + /// Load [`Options`] from a `uv.toml` file. fn read_file(path: &Path) -> Result { let content = fs_err::read_to_string(path)?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 37f9755437d93..79b286e63c34e 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -117,17 +117,19 @@ async fn run(mut cli: Cli) -> Result { None } else if matches!(&*cli.command, Commands::Tool(_)) { // For commands that operate at the user-level, ignore local configuration. - FilesystemOptions::user()? + FilesystemOptions::system()?.combine(FilesystemOptions::user()?) } else if let Ok(workspace) = Workspace::discover(&project_dir, &DiscoveryOptions::default()).await { let project = FilesystemOptions::find(workspace.install_path())?; + let system = FilesystemOptions::system()?; let user = FilesystemOptions::user()?; - project.combine(user) + project.combine(system).combine(user) } else { let project = FilesystemOptions::find(&project_dir)?; + let system = FilesystemOptions::system()?; let user = FilesystemOptions::user()?; - project.combine(user) + project.combine(system).combine(user) }; // Parse the external command, if necessary. From bbf9ce24f3122b97d91e490e392db708ee599f9c Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 12:16:55 -0700 Subject: [PATCH 02/21] Read XDG_CONFIG_DIRS to determine the system config file. --- crates/uv-settings/src/lib.rs | 106 ++++++++++++++---- .../tests/fixtures/first/uv/uv.toml | 0 crates/uv-settings/tests/fixtures/uv/uv.toml | 2 + 3 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 crates/uv-settings/tests/fixtures/first/uv/uv.toml create mode 100644 crates/uv-settings/tests/fixtures/uv/uv.toml diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 833a141af41e2..051ebf7ec6f8c 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -62,21 +62,16 @@ impl FilesystemOptions { } pub fn system() -> Result, Error> { - let dir = system_config_dir(); - let root = dir.join("uv"); - let file = root.join("uv.toml"); - - debug!( - "Searching for system configuration in: `{}`", - file.display() - ); - match read_file(&file) { - Ok(options) => { - debug!("Found system configuration in: `{}`", file.display()); - Ok(Some(Self(options))) + if let Some(file) = system_config_file() { + match read_file(&file) { + Ok(options) => { + debug!("Found system configuration in: `{}`", file.display()); + Ok(Some(Self(options))) + } + Err(err) => Err(err), } - Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(err) => Err(err), + } else { + Ok(None) } } @@ -206,20 +201,45 @@ fn user_config_dir() -> Option { } } -/// Returns the path to the system configuration directory. +fn locate_system_config_xdg() -> Option { + // On Linux/MacOS systems, read the XDG_CONFIG_DIRS environment variable + if let Ok(config_dirs) = std::env::var("XDG_CONFIG_DIRS") { + for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { + let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); + + if uv_toml_path.is_file() { + return Some(uv_toml_path); + } + } + } + None +} + +/// Returns the path to the system configuration file. /// -/// Right now this defaults to "/etc/" in *nix systems and "C:\ProgramData" on Windows. -fn system_config_dir() -> PathBuf { +/// Unix-like systems: This uses the `XDG_CONFIG_DIRS` environment variable in *nix systems. +/// If the var is not present it will check /etc/xdg/uv/uv.toml and then /etc/uv/uv.toml. +/// Windows: "C:\ProgramData\uv\uv.toml" is used. +fn system_config_file() -> Option { // On Windows, use, e.g., C:\ProgramData #[cfg(windows)] { - PathBuf::from("C:\\ProgramData") + PathBuf::from("C:\\ProgramData\\uv\\uv.toml") } - // On Linux and macOS, use /etc/uv/uv.toml. #[cfg(not(windows))] { - PathBuf::from("/etc") + if let Some(path) = locate_system_config_xdg() { + return Some(path); + } + // Fallback to /etc/xdg/uv/uv.toml then /etc/uv/uv.toml if XDG_CONFIG_DIRS is not set or no + // valid path is found + for candidate in ["/etc/xdg/uv/uv.toml", "/etc/uv/uv.toml"] { + if Path::new(candidate).is_file() { + return Some(PathBuf::from(candidate)); + } + } + None } } @@ -242,3 +262,49 @@ pub enum Error { #[error("Failed to parse: `{0}`")] UvToml(String, #[source] toml::de::Error), } + +#[cfg(test)] +mod test { + use crate::locate_system_config_xdg; + use std::env; + use std::path::Path; + + #[test] + fn test_locate_system_config_xdg() { + // Construct the path to the uv.toml file in the tests/fixture directory + let td = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures"); + let tf = td.join("uv").join("uv.toml"); + + let cur_dir = env::current_dir().unwrap(); + { + env::set_current_dir(&td).unwrap(); + + // Empty string + env::set_var("XDG_CONFIG_DIRS", ""); + assert_eq!(locate_system_config_xdg(), None); + + // Single colon + env::set_var("XDG_CONFIG_DIRS", ":"); + assert_eq!(locate_system_config_xdg(), None); + } + + env::set_current_dir(&cur_dir).unwrap(); + assert_eq!(locate_system_config_xdg(), None); + + // Set the environment variable to the directory containing the uv.toml file + env::set_var("XDG_CONFIG_DIRS", td.clone()); + + // Assert that the system_config_file function returns the correct path + assert_eq!(locate_system_config_xdg().unwrap(), tf); + + let first_td = td.join("first"); + let first_tf = first_td.join("uv").join("uv.toml"); + env::set_var( + "XDG_CONFIG_DIRS", + format!("{}:{}", first_td.to_string_lossy(), td.to_string_lossy()), + ); + assert_eq!(locate_system_config_xdg().unwrap(), first_tf); + } +} diff --git a/crates/uv-settings/tests/fixtures/first/uv/uv.toml b/crates/uv-settings/tests/fixtures/first/uv/uv.toml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/uv-settings/tests/fixtures/uv/uv.toml b/crates/uv-settings/tests/fixtures/uv/uv.toml new file mode 100644 index 0000000000000..042ea042f9ddf --- /dev/null +++ b/crates/uv-settings/tests/fixtures/uv/uv.toml @@ -0,0 +1,2 @@ +[pip] +index-url = "https://test.pypi.org/simple" From 1b04ab4a0d912037011607103ec582a41f33c6c8 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 13:16:44 -0700 Subject: [PATCH 03/21] Simplify FilesystemOptions.system(). --- crates/uv-settings/src/lib.rs | 12 ++++-------- crates/uv/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 051ebf7ec6f8c..30e2e54c2fee9 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -63,13 +63,8 @@ impl FilesystemOptions { pub fn system() -> Result, Error> { if let Some(file) = system_config_file() { - match read_file(&file) { - Ok(options) => { - debug!("Found system configuration in: `{}`", file.display()); - Ok(Some(Self(options))) - } - Err(err) => Err(err), - } + let options = read_file(&file)?; + Ok(Some(Self(options))) } else { Ok(None) } @@ -269,9 +264,10 @@ mod test { use std::env; use std::path::Path; + #[cfg(not(windows))] #[test] fn test_locate_system_config_xdg() { - // Construct the path to the uv.toml file in the tests/fixture directory + // Construct the path to the uv.toml file in the tests/fixtures directory let td = Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("fixtures"); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 79b286e63c34e..f10a2cbaa14d7 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -129,7 +129,7 @@ async fn run(mut cli: Cli) -> Result { let project = FilesystemOptions::find(&project_dir)?; let system = FilesystemOptions::system()?; let user = FilesystemOptions::user()?; - project.combine(system).combine(user) + project.combine(user).combine(system) }; // Parse the external command, if necessary. From f51a3450b6b24db0b9d50d898fffe188cbb1beaa Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 13:23:33 -0700 Subject: [PATCH 04/21] Idiomatic(?) rust for FilesystemOptions.system(). --- crates/uv-settings/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 30e2e54c2fee9..255673524a27c 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -62,12 +62,11 @@ impl FilesystemOptions { } pub fn system() -> Result, Error> { - if let Some(file) = system_config_file() { - let options = read_file(&file)?; - Ok(Some(Self(options))) - } else { - Ok(None) - } + let Some(file) = system_config_file() else { + return Ok(None); + }; + debug!("Found system configuration in: `{}`", file.display()); + Ok(Some(Self(read_file(&file)?))) } /// Find the [`FilesystemOptions`] for the given path. From 6833a99c43f45c9e1697485fce35a0024271dfd1 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 14:29:23 -0700 Subject: [PATCH 05/21] Make the entire mod test not applicable to windows. --- crates/uv-settings/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 255673524a27c..2e2e93463cca6 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -258,12 +258,12 @@ pub enum Error { } #[cfg(test)] +#[cfg(not(windows))] mod test { use crate::locate_system_config_xdg; use std::env; use std::path::Path; - #[cfg(not(windows))] #[test] fn test_locate_system_config_xdg() { // Construct the path to the uv.toml file in the tests/fixtures directory From 99737edfc204c37ebf93cc9fe546d9144a3eda82 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 15:30:59 -0700 Subject: [PATCH 06/21] Conform to the spec of XDG_CONFIG_DIRS strictly. --- crates/uv-settings/src/lib.rs | 53 +++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 2e2e93463cca6..c234f0d5811d0 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -195,15 +195,17 @@ fn user_config_dir() -> Option { } } -fn locate_system_config_xdg() -> Option { +fn locate_system_config_xdg(value: Option<&str>) -> Option { // On Linux/MacOS systems, read the XDG_CONFIG_DIRS environment variable - if let Ok(config_dirs) = std::env::var("XDG_CONFIG_DIRS") { - for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { - let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); - if uv_toml_path.is_file() { - return Some(uv_toml_path); - } + let default = "/etc/xdg"; + let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default); + + for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { + let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); + + if uv_toml_path.is_file() { + return Some(uv_toml_path); } } None @@ -223,7 +225,9 @@ fn system_config_file() -> Option { #[cfg(not(windows))] { - if let Some(path) = locate_system_config_xdg() { + if let Some(path) = + locate_system_config_xdg(std::env::var("XDG_CONFIG_DIRS").ok().as_deref()) + { return Some(path); } // Fallback to /etc/xdg/uv/uv.toml then /etc/uv/uv.toml if XDG_CONFIG_DIRS is not set or no @@ -267,39 +271,40 @@ mod test { #[test] fn test_locate_system_config_xdg() { // Construct the path to the uv.toml file in the tests/fixtures directory - let td = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("fixtures"); + let td = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"); let tf = td.join("uv").join("uv.toml"); let cur_dir = env::current_dir().unwrap(); { env::set_current_dir(&td).unwrap(); + // None + assert_eq!(locate_system_config_xdg(None), None); + // Empty string - env::set_var("XDG_CONFIG_DIRS", ""); - assert_eq!(locate_system_config_xdg(), None); + assert_eq!(locate_system_config_xdg(Some("")), None); // Single colon - env::set_var("XDG_CONFIG_DIRS", ":"); - assert_eq!(locate_system_config_xdg(), None); + assert_eq!(locate_system_config_xdg(Some(":")), None); } env::set_current_dir(&cur_dir).unwrap(); - assert_eq!(locate_system_config_xdg(), None); - - // Set the environment variable to the directory containing the uv.toml file - env::set_var("XDG_CONFIG_DIRS", td.clone()); + assert_eq!(locate_system_config_xdg(Some(":")), None); // Assert that the system_config_file function returns the correct path - assert_eq!(locate_system_config_xdg().unwrap(), tf); + assert_eq!( + locate_system_config_xdg(Some(td.to_str().unwrap())).unwrap(), + tf + ); let first_td = td.join("first"); let first_tf = first_td.join("uv").join("uv.toml"); - env::set_var( - "XDG_CONFIG_DIRS", - format!("{}:{}", first_td.to_string_lossy(), td.to_string_lossy()), + assert_eq!( + locate_system_config_xdg(Some( + format!("{}:{}", first_td.to_string_lossy(), td.to_string_lossy()).as_str() + )) + .unwrap(), + first_tf ); - assert_eq!(locate_system_config_xdg().unwrap(), first_tf); } } From 8a8bd894deeb665ac4d7a2900cff1766ea3a4689 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 15:33:40 -0700 Subject: [PATCH 07/21] Fix windows PathBuf. --- crates/uv-settings/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index c234f0d5811d0..9ef3e16c06fba 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -220,7 +220,7 @@ fn system_config_file() -> Option { // On Windows, use, e.g., C:\ProgramData #[cfg(windows)] { - PathBuf::from("C:\\ProgramData\\uv\\uv.toml") + Some(PathBuf::from("C:\\ProgramData\\uv\\uv.toml")) } #[cfg(not(windows))] From d92a61cdc3d1139bcb21caa01fcf12d8e8a4a38e Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 15:39:30 -0700 Subject: [PATCH 08/21] Fix window compilation issues. --- crates/uv-settings/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 9ef3e16c06fba..f50c7b4dacdc9 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -195,6 +195,7 @@ fn user_config_dir() -> Option { } } +#[cfg(not(windows))] fn locate_system_config_xdg(value: Option<&str>) -> Option { // On Linux/MacOS systems, read the XDG_CONFIG_DIRS environment variable @@ -220,7 +221,7 @@ fn system_config_file() -> Option { // On Windows, use, e.g., C:\ProgramData #[cfg(windows)] { - Some(PathBuf::from("C:\\ProgramData\\uv\\uv.toml")) + return Some(PathBuf::from("C:\\ProgramData\\uv\\uv.toml")); } #[cfg(not(windows))] From 3cce32a5b3d9b4f82f4c88876011bc81eb6d0b64 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 19:02:10 -0700 Subject: [PATCH 09/21] Add XDG_CONFIG_DIRS to uv_static. --- crates/uv-settings/src/lib.rs | 51 ++++++++++++++++++++++++-------- crates/uv-static/src/env_vars.rs | 3 ++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index f50c7b4dacdc9..6967074e7eab6 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -216,29 +216,29 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { /// /// Unix-like systems: This uses the `XDG_CONFIG_DIRS` environment variable in *nix systems. /// If the var is not present it will check /etc/xdg/uv/uv.toml and then /etc/uv/uv.toml. -/// Windows: "C:\ProgramData\uv\uv.toml" is used. +/// Windows: "%SYSTEMDRIVE%\ProgramData\uv\uv.toml" is used. fn system_config_file() -> Option { // On Windows, use, e.g., C:\ProgramData #[cfg(windows)] { - return Some(PathBuf::from("C:\\ProgramData\\uv\\uv.toml")); + if let Ok(system_drive) = std::env::var(EnvVars::SYSTEMDRIVE) { + let candidate = PathBuf::from(system_drive).join("ProgramData\\uv\\uv.toml"); + return candidate.as_path().is_file().then(|| candidate); + } + None } #[cfg(not(windows))] { if let Some(path) = - locate_system_config_xdg(std::env::var("XDG_CONFIG_DIRS").ok().as_deref()) + locate_system_config_xdg(std::env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref()) { return Some(path); } - // Fallback to /etc/xdg/uv/uv.toml then /etc/uv/uv.toml if XDG_CONFIG_DIRS is not set or no - // valid path is found - for candidate in ["/etc/xdg/uv/uv.toml", "/etc/uv/uv.toml"] { - if Path::new(candidate).is_file() { - return Some(PathBuf::from(candidate)); - } - } - None + // Fallback /etc/uv/uv.toml if XDG_CONFIG_DIRS is not set or no valid + // path is found + let candidate = Path::new("/etc/uv/uv.toml"); + candidate.is_file().then(|| candidate.to_path_buf()) } } @@ -263,13 +263,19 @@ pub enum Error { } #[cfg(test)] -#[cfg(not(windows))] mod test { + #[cfg(not(windows))] use crate::locate_system_config_xdg; + #[cfg(windows)] + use crate::system_config_file; + #[cfg(windows)] + use uv_static::EnvVars; + use std::env; use std::path::Path; #[test] + #[cfg(not(windows))] fn test_locate_system_config_xdg() { // Construct the path to the uv.toml file in the tests/fixtures directory let td = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"); @@ -308,4 +314,25 @@ mod test { first_tf ); } + + #[cfg(windows)] + fn test_windows_config() { + // Construct the path to the uv.toml file in the tests/fixtures directory + let td = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests\\fixtures"); + let expected = td.join("ProgramData\\uv\\uv.toml"); + + // Previous value of %SYSTEMDRIVE% which should always exist + let sd = env::var(EnvVars::SYSTEMDRIVE).unwrap(); + + // This is typically only a drive (that is, letter and colon) but we + // allow anything, including a path to the test fixtures... + env::set_var(EnvVars::SYSTEMDRIVE, td.clone()); + assert_eq!(system_config_file().unwrap(), expected); + + // This does not have a ProgramData child, so contains no config. + env::set_var(EnvVars::SYSTEMDRIVE, td.parent().unwrap()); + assert_eq!(system_config_file(), None); + + env::set_var(EnvVars::SYSTEMDRIVE, sd); + } } diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 7137d895d1d5d..8ce275a74cd09 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -199,6 +199,9 @@ impl EnvVars { /// Used to set a temporary directory for some tests. pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR"; + /// Path to system-level configuration directory on Unix systems. + pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS"; + /// Path to user-level configuration directory on Unix systems. pub const XDG_CONFIG_HOME: &'static str = "XDG_CONFIG_HOME"; From e93dac76d3e2b6a4b28da803f222e3fc93fb6dbe Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 19:05:05 -0700 Subject: [PATCH 10/21] Adjust the priority of combining configs. --- crates/uv/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index f10a2cbaa14d7..aea2401067c65 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -117,14 +117,14 @@ async fn run(mut cli: Cli) -> Result { None } else if matches!(&*cli.command, Commands::Tool(_)) { // For commands that operate at the user-level, ignore local configuration. - FilesystemOptions::system()?.combine(FilesystemOptions::user()?) + FilesystemOptions::user()?.combine(FilesystemOptions::system()?) } else if let Ok(workspace) = Workspace::discover(&project_dir, &DiscoveryOptions::default()).await { let project = FilesystemOptions::find(workspace.install_path())?; let system = FilesystemOptions::system()?; let user = FilesystemOptions::user()?; - project.combine(system).combine(user) + project.combine(user).combine(system) } else { let project = FilesystemOptions::find(&project_dir)?; let system = FilesystemOptions::system()?; From a2fb764d268b61f439df812eb1a6dc6cb3275294 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 19:35:46 -0700 Subject: [PATCH 11/21] Add SYSTEMDRIVE to env_vars for windows. --- crates/uv-static/src/env_vars.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 8ce275a74cd09..0c0ff7fc8756a 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -202,6 +202,9 @@ impl EnvVars { /// Path to system-level configuration directory on Unix systems. pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS"; + /// Path to system-level configuration directory on Windows systems. + pub const SYSTEMDRIVE: &'static str = "%SYSTEMDRIVE%"; + /// Path to user-level configuration directory on Unix systems. pub const XDG_CONFIG_HOME: &'static str = "XDG_CONFIG_HOME"; From 8bc805520648a7f5b22f55349d5fa3c38b38374a Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 19:44:05 -0700 Subject: [PATCH 12/21] EnvVars is used by windows too. --- crates/uv-settings/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 6967074e7eab6..873aa3e0602f8 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -4,7 +4,6 @@ use std::path::{Path, PathBuf}; use tracing::debug; use uv_fs::Simplified; -#[cfg(not(windows))] use uv_static::EnvVars; use uv_warnings::warn_user; From 7f4ed6e0acaf96615458641bdfdada3bc656ad0e Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 19:56:39 -0700 Subject: [PATCH 13/21] Make clippy happy. --- crates/uv-settings/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 873aa3e0602f8..d4047fd416dfc 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -222,7 +222,7 @@ fn system_config_file() -> Option { { if let Ok(system_drive) = std::env::var(EnvVars::SYSTEMDRIVE) { let candidate = PathBuf::from(system_drive).join("ProgramData\\uv\\uv.toml"); - return candidate.as_path().is_file().then(|| candidate); + return candidate.as_path().is_file().then_some(candidate); } None } From 7d586b94aa803bc09184123f685c0b7bd349cca7 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 20:00:02 -0700 Subject: [PATCH 14/21] Mark the windows test. --- crates/uv-settings/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index d4047fd416dfc..6fbbbe736c017 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -314,6 +314,7 @@ mod test { ); } + #[test] #[cfg(windows)] fn test_windows_config() { // Construct the path to the uv.toml file in the tests/fixtures directory From e27d93ad435b594846d5669245387bf2da464a33 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Fri, 18 Oct 2024 20:17:14 -0700 Subject: [PATCH 15/21] Fix for missing SYSTEMDRIVE env var in windows for tests. --- crates/uv-settings/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 6fbbbe736c017..fbadf9996f515 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -322,7 +322,7 @@ mod test { let expected = td.join("ProgramData\\uv\\uv.toml"); // Previous value of %SYSTEMDRIVE% which should always exist - let sd = env::var(EnvVars::SYSTEMDRIVE).unwrap(); + let sd = env::var(EnvVars::SYSTEMDRIVE).unwrap_or("C:".to_string()); // This is typically only a drive (that is, letter and colon) but we // allow anything, including a path to the test fixtures... From 612d76676fe26198546816f33a63017c7b467fa6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Oct 2024 19:50:44 -0400 Subject: [PATCH 16/21] Remove percents; minor comments --- crates/uv-settings/src/lib.rs | 21 ++++++++++----------- crates/uv-static/src/env_vars.rs | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index fbadf9996f515..1cd6408d518cc 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -1,3 +1,4 @@ +use std::env; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -179,16 +180,16 @@ impl From for FilesystemOptions { /// `XDG_CONFIG_HOME` environment variable on both Linux _and_ macOS, rather than the /// `Application Support` directory on macOS. fn user_config_dir() -> Option { - // On Windows, use, e.g., C:\Users\Alice\AppData\Roaming + // On Windows, use, e.g., `C:\Users\Alice\AppData\Roaming`. #[cfg(windows)] { dirs_sys::known_folder_roaming_app_data() } - // On Linux and macOS, use, e.g., /home/alice/.config. + // On Linux and macOS, use, e.g., `/home/alice/.config`. #[cfg(not(windows))] { - std::env::var_os(EnvVars::XDG_CONFIG_HOME) + env::var_os(EnvVars::XDG_CONFIG_HOME) .and_then(dirs_sys::is_absolute_path) .or_else(|| dirs_sys::home_dir().map(|path| path.join(".config"))) } @@ -196,14 +197,12 @@ fn user_config_dir() -> Option { #[cfg(not(windows))] fn locate_system_config_xdg(value: Option<&str>) -> Option { - // On Linux/MacOS systems, read the XDG_CONFIG_DIRS environment variable - + // On Linux and macOS, read the `XDG_CONFIG_DIRS` environment variable. let default = "/etc/xdg"; let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default); for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); - if uv_toml_path.is_file() { return Some(uv_toml_path); } @@ -217,10 +216,10 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { /// If the var is not present it will check /etc/xdg/uv/uv.toml and then /etc/uv/uv.toml. /// Windows: "%SYSTEMDRIVE%\ProgramData\uv\uv.toml" is used. fn system_config_file() -> Option { - // On Windows, use, e.g., C:\ProgramData + // On Windows, use, e.g., `C:\ProgramData`. #[cfg(windows)] { - if let Ok(system_drive) = std::env::var(EnvVars::SYSTEMDRIVE) { + if let Ok(system_drive) = env::var(EnvVars::SYSTEMDRIVE) { let candidate = PathBuf::from(system_drive).join("ProgramData\\uv\\uv.toml"); return candidate.as_path().is_file().then_some(candidate); } @@ -230,12 +229,12 @@ fn system_config_file() -> Option { #[cfg(not(windows))] { if let Some(path) = - locate_system_config_xdg(std::env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref()) + locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref()) { return Some(path); } - // Fallback /etc/uv/uv.toml if XDG_CONFIG_DIRS is not set or no valid - // path is found + // Fallback to `/etc/uv/uv.toml` if `XDG_CONFIG_DIRS` is not set or no valid + // path is found. let candidate = Path::new("/etc/uv/uv.toml"); candidate.is_file().then(|| candidate.to_path_buf()) } diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 0c0ff7fc8756a..fa5eb8de45e61 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -203,7 +203,7 @@ impl EnvVars { pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS"; /// Path to system-level configuration directory on Windows systems. - pub const SYSTEMDRIVE: &'static str = "%SYSTEMDRIVE%"; + pub const SYSTEMDRIVE: &'static str = "SYSTEMDRIVE"; /// Path to user-level configuration directory on Unix systems. pub const XDG_CONFIG_HOME: &'static str = "XDG_CONFIG_HOME"; From 4e53cb9a6c7ae51cf0ef2a460305b989d04021a9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Oct 2024 20:01:14 -0400 Subject: [PATCH 17/21] Use fixtures for tests --- Cargo.lock | 15 ++++ crates/uv-settings/Cargo.toml | 17 ++++ crates/uv-settings/src/lib.rs | 90 +++++++++++++------ .../tests/fixtures/first/uv/uv.toml | 0 crates/uv-settings/tests/fixtures/uv/uv.toml | 2 - 5 files changed, 93 insertions(+), 31 deletions(-) delete mode 100644 crates/uv-settings/tests/fixtures/first/uv/uv.toml delete mode 100644 crates/uv-settings/tests/fixtures/uv/uv.toml diff --git a/Cargo.lock b/Cargo.lock index 15629dbe29d78..cf7e83d11a6e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5136,11 +5136,25 @@ dependencies = [ name = "uv-settings" version = "0.0.1" dependencies = [ + "assert_cmd", + "assert_fs", + "base64 0.22.1", + "byteorder", "clap", "dirs-sys", + "etcetera", + "filetime", "fs-err", + "ignore", + "indoc", + "insta", + "predicates", + "regex", + "reqwest", "schemars", "serde", + "similar", + "tempfile", "textwrap", "thiserror", "toml", @@ -5160,6 +5174,7 @@ dependencies = [ "uv-resolver", "uv-static", "uv-warnings", + "zip", ] [[package]] diff --git a/crates/uv-settings/Cargo.toml b/crates/uv-settings/Cargo.toml index 683b9aae3c8c7..f9c4ab9edced4 100644 --- a/crates/uv-settings/Cargo.toml +++ b/crates/uv-settings/Cargo.toml @@ -44,3 +44,20 @@ url = { workspace = true } [package.metadata.cargo-shear] ignored = ["uv-options-metadata", "clap"] + +[dev-dependencies] +assert_cmd = { version = "2.0.16" } +assert_fs = { version = "1.1.2" } +base64 = { version = "0.22.1" } +byteorder = { version = "1.5.0" } +etcetera = { workspace = true } +filetime = { version = "0.2.25" } +ignore = { version = "0.4.23" } +indoc = { version = "2.0.5" } +insta = { version = "1.40.0", features = ["filters", "json"] } +predicates = { version = "3.1.2" } +regex = { workspace = true } +reqwest = { workspace = true, features = ["blocking"], default-features = false } +similar = { version = "2.6.0" } +tempfile = { workspace = true } +zip = { workspace = true } diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 1cd6408d518cc..2065d430721d4 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -201,8 +201,9 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { let default = "/etc/xdg"; let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default); - for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { - let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); + for dir in env::split_paths(config_dirs) { + println!("dir: {:?}", dir); + let uv_toml_path = dir.join("uv").join("uv.toml"); if uv_toml_path.is_file() { return Some(uv_toml_path); } @@ -269,19 +270,27 @@ mod test { #[cfg(windows)] use uv_static::EnvVars; + use assert_fs::fixture::FixtureError; + use assert_fs::prelude::*; + use indoc::indoc; use std::env; - use std::path::Path; #[test] #[cfg(not(windows))] - fn test_locate_system_config_xdg() { - // Construct the path to the uv.toml file in the tests/fixtures directory - let td = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"); - let tf = td.join("uv").join("uv.toml"); + fn test_locate_system_config_xdg() -> Result<(), FixtureError> { + // Write a `uv.toml` to a temporary directory. + let context = assert_fs::TempDir::new()?; + context.child("uv").child("uv.toml").write_str(indoc! { + r#" + [pip] + index-url = "https://test.pypi.org/simple" + "#, + })?; + + let current_dir = env::current_dir().unwrap(); - let cur_dir = env::current_dir().unwrap(); { - env::set_current_dir(&td).unwrap(); + env::set_current_dir(context.path()).unwrap(); // None assert_eq!(locate_system_config_xdg(None), None); @@ -293,45 +302,68 @@ mod test { assert_eq!(locate_system_config_xdg(Some(":")), None); } - env::set_current_dir(&cur_dir).unwrap(); + env::set_current_dir(¤t_dir).unwrap(); assert_eq!(locate_system_config_xdg(Some(":")), None); - // Assert that the system_config_file function returns the correct path + // Assert that the `system_config_file` function returns the correct path. assert_eq!( - locate_system_config_xdg(Some(td.to_str().unwrap())).unwrap(), - tf + locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(), + context.child("uv").child("uv.toml").path() ); - let first_td = td.join("first"); - let first_tf = first_td.join("uv").join("uv.toml"); + // Write a separate `uv.toml` to a different directory. + let first = context.child("first"); + let first_config = first.child("uv").child("uv.toml"); + first_config.write_str("")?; + assert_eq!( locate_system_config_xdg(Some( - format!("{}:{}", first_td.to_string_lossy(), td.to_string_lossy()).as_str() + format!("{}:{}", first.to_string_lossy(), context.to_string_lossy()).as_str() )) .unwrap(), - first_tf + first_config.path() ); + + Ok(()) } #[test] #[cfg(windows)] - fn test_windows_config() { - // Construct the path to the uv.toml file in the tests/fixtures directory - let td = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests\\fixtures"); - let expected = td.join("ProgramData\\uv\\uv.toml"); - - // Previous value of %SYSTEMDRIVE% which should always exist - let sd = env::var(EnvVars::SYSTEMDRIVE).unwrap_or("C:".to_string()); + fn test_windows_config() -> Result<(), FixtureError> { + // Write a `uv.toml` to a temporary directory. + let context = assert_fs::TempDir::new()?; + context + .child("ProgramData") + .child("uv") + .child("uv.toml") + .write_str(indoc! { + r#" + [pip] + index-url = "https://test.pypi.org/simple" + "#})?; + + // Previous value of `SYSTEMDRIVE` which should always exist. + let prev_system_drive = env::var(EnvVars::SYSTEMDRIVE).unwrap_or("C:".to_string()); // This is typically only a drive (that is, letter and colon) but we // allow anything, including a path to the test fixtures... - env::set_var(EnvVars::SYSTEMDRIVE, td.clone()); - assert_eq!(system_config_file().unwrap(), expected); + env::set_var(EnvVars::SYSTEMDRIVE, context.path().as_os_str()); + assert_eq!( + system_config_file().unwrap(), + context + .child("ProgramData") + .child("uv") + .child("uv.toml") + .path() + ); - // This does not have a ProgramData child, so contains no config. - env::set_var(EnvVars::SYSTEMDRIVE, td.parent().unwrap()); + // This does not have a `ProgramData` child, so contains no config. + let context = assert_fs::TempDir::new()?; + env::set_var(EnvVars::SYSTEMDRIVE, context.path().as_os_str()); assert_eq!(system_config_file(), None); - env::set_var(EnvVars::SYSTEMDRIVE, sd); + env::set_var(EnvVars::SYSTEMDRIVE, prev_system_drive); + + Ok(()) } } diff --git a/crates/uv-settings/tests/fixtures/first/uv/uv.toml b/crates/uv-settings/tests/fixtures/first/uv/uv.toml deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/crates/uv-settings/tests/fixtures/uv/uv.toml b/crates/uv-settings/tests/fixtures/uv/uv.toml deleted file mode 100644 index 042ea042f9ddf..0000000000000 --- a/crates/uv-settings/tests/fixtures/uv/uv.toml +++ /dev/null @@ -1,2 +0,0 @@ -[pip] -index-url = "https://test.pypi.org/simple" From fc36dbd73aa40ef3bb4b24ad95ec478856768a17 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Oct 2024 20:11:02 -0400 Subject: [PATCH 18/21] Fixups --- Cargo.lock | 13 ------------- crates/uv-settings/Cargo.toml | 13 ------------- crates/uv-settings/src/lib.rs | 2 +- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf7e83d11a6e5..7a843975da0d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5136,25 +5136,13 @@ dependencies = [ name = "uv-settings" version = "0.0.1" dependencies = [ - "assert_cmd", "assert_fs", - "base64 0.22.1", - "byteorder", "clap", "dirs-sys", - "etcetera", - "filetime", "fs-err", - "ignore", "indoc", - "insta", - "predicates", - "regex", - "reqwest", "schemars", "serde", - "similar", - "tempfile", "textwrap", "thiserror", "toml", @@ -5174,7 +5162,6 @@ dependencies = [ "uv-resolver", "uv-static", "uv-warnings", - "zip", ] [[package]] diff --git a/crates/uv-settings/Cargo.toml b/crates/uv-settings/Cargo.toml index f9c4ab9edced4..1aca3d09c1f52 100644 --- a/crates/uv-settings/Cargo.toml +++ b/crates/uv-settings/Cargo.toml @@ -46,18 +46,5 @@ url = { workspace = true } ignored = ["uv-options-metadata", "clap"] [dev-dependencies] -assert_cmd = { version = "2.0.16" } assert_fs = { version = "1.1.2" } -base64 = { version = "0.22.1" } -byteorder = { version = "1.5.0" } -etcetera = { workspace = true } -filetime = { version = "0.2.25" } -ignore = { version = "0.4.23" } indoc = { version = "2.0.5" } -insta = { version = "1.40.0", features = ["filters", "json"] } -predicates = { version = "3.1.2" } -regex = { workspace = true } -reqwest = { workspace = true, features = ["blocking"], default-features = false } -similar = { version = "2.6.0" } -tempfile = { workspace = true } -zip = { workspace = true } diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 2065d430721d4..a189ff7f95a9d 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -337,7 +337,7 @@ mod test { .child("uv") .child("uv.toml") .write_str(indoc! { - r#" + r#" [pip] index-url = "https://test.pypi.org/simple" "#})?; From ee9b6d6cd2d7ee2f79a3a41090ef720899264f53 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Oct 2024 20:15:16 -0400 Subject: [PATCH 19/21] Add docs --- crates/uv-settings/src/lib.rs | 4 +--- docs/configuration/files.md | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index a189ff7f95a9d..464fe1ccb606e 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -202,7 +202,6 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default); for dir in env::split_paths(config_dirs) { - println!("dir: {:?}", dir); let uv_toml_path = dir.join("uv").join("uv.toml"); if uv_toml_path.is_file() { return Some(uv_toml_path); @@ -336,8 +335,7 @@ mod test { .child("ProgramData") .child("uv") .child("uv.toml") - .write_str(indoc! { - r#" + .write_str(indoc! { r#" [pip] index-url = "https://test.pypi.org/simple" "#})?; diff --git a/docs/configuration/files.md b/docs/configuration/files.md index 58c6dc99550a5..fbff28e7b2a7b 100644 --- a/docs/configuration/files.md +++ b/docs/configuration/files.md @@ -9,7 +9,7 @@ in the nearest parent directory. For `tool` commands, which operate at the user level, local configuration files will be ignored. Instead, uv will exclusively read from user-level configuration - (e.g., `~/.config/uv/uv.toml`). + (e.g., `~/.config/uv/uv.toml`) and system-level configuration (e.g., `/etc/uv/uv.toml`). In workspaces, uv will begin its search at the workspace root, ignoring any configuration defined in workspace members. Since the workspace is locked as a single unit, configuration is shared across @@ -40,13 +40,19 @@ index-url = "https://test.pypi.org/simple" `[tool.uv]` section in the accompanying `pyproject.toml` will be ignored. uv will also discover user-level configuration at `~/.config/uv/uv.toml` (or -`$XDG_CONFIG_HOME/uv/uv.toml`) on macOS and Linux, or `%APPDATA%\uv\uv.toml` on Windows. User-level -configuration must use the `uv.toml` format, rather than the `pyproject.toml` format, as a -`pyproject.toml` is intended to define a Python _project_. +`$XDG_CONFIG_HOME/uv/uv.toml`) on macOS and Linux, or `%APPDATA%\uv\uv.toml` on Windows; and +system-level configuration at `/etc/uv/uv.toml` (or `$XDG_CONFIG_DIRS/uv/uv.toml`) on macOS and +Linux, or `%SYSTEMDRIVE%\ProgramData\uv\uv.toml` on Windows. -If both project- and user-level configuration are found, the settings will be merged, with the -project-level configuration taking precedence. Specifically, if a string, number, or boolean is -present in both tables, the project-level value will be used, and the user-level value will be +User-and system-level configuration must use the `uv.toml` format, rather than the `pyproject.toml` +format, as a `pyproject.toml` is intended to define a Python _project_. + +If project-, user-, and system-level configuration files are found, the settings will be merged, +with project-level configuration taking precedence over the user-level configuration, and user-level +configuration taking precedence over the system-level configuration. + +For example, if a string, number, or boolean is present in both the project- and user-level +configuration tables, the project-level value will be used, and the user-level value will be ignored. If an array is present in both tables, the arrays will be concatenated, with the project-level settings appearing earlier in the merged array. From 138c545f960e2e4578b490281b716519a11f994a Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 20 Oct 2024 20:27:48 -0400 Subject: [PATCH 20/21] Remove split_paths --- crates/uv-settings/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 464fe1ccb606e..ad54be5a5a9f0 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -201,8 +201,8 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { let default = "/etc/xdg"; let config_dirs = value.filter(|s| !s.is_empty()).unwrap_or(default); - for dir in env::split_paths(config_dirs) { - let uv_toml_path = dir.join("uv").join("uv.toml"); + for dir in config_dirs.split(':').take_while(|s| !s.is_empty()) { + let uv_toml_path = Path::new(dir).join("uv").join("uv.toml"); if uv_toml_path.is_file() { return Some(uv_toml_path); } From 9185df8c12bdfeda8ba0c2fa4e7d772c7bc25cbf Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 21 Oct 2024 13:17:27 -0400 Subject: [PATCH 21/21] Remove env vars in tests --- crates/uv-settings/src/lib.rs | 63 +++++++++++++++-------------------- docs/configuration/files.md | 4 ++- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index ad54be5a5a9f0..80ce9fd16f626 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -210,20 +210,24 @@ fn locate_system_config_xdg(value: Option<&str>) -> Option { None } +#[cfg(windows)] +fn locate_system_config_windows(system_drive: &std::ffi::OsStr) -> Option { + // On Windows, use `%SYSTEMDRIVE%\ProgramData\uv\uv.toml` (e.g., `C:\ProgramData`). + let candidate = PathBuf::from(system_drive).join("ProgramData\\uv\\uv.toml"); + candidate.as_path().is_file().then_some(candidate) +} + /// Returns the path to the system configuration file. /// -/// Unix-like systems: This uses the `XDG_CONFIG_DIRS` environment variable in *nix systems. -/// If the var is not present it will check /etc/xdg/uv/uv.toml and then /etc/uv/uv.toml. -/// Windows: "%SYSTEMDRIVE%\ProgramData\uv\uv.toml" is used. +/// On Unix-like systems, uses the `XDG_CONFIG_DIRS` environment variable (falling back to +/// `/etc/xdg/uv/uv.toml` if unset or empty) and then `/etc/uv/uv.toml` +/// +/// On Windows, uses `%SYSTEMDRIVE%\ProgramData\uv\uv.toml`. fn system_config_file() -> Option { - // On Windows, use, e.g., `C:\ProgramData`. #[cfg(windows)] { - if let Ok(system_drive) = env::var(EnvVars::SYSTEMDRIVE) { - let candidate = PathBuf::from(system_drive).join("ProgramData\\uv\\uv.toml"); - return candidate.as_path().is_file().then_some(candidate); - } - None + env::var_os(EnvVars::SYSTEMDRIVE) + .and_then(|system_drive| locate_system_config_windows(&system_drive)) } #[cfg(not(windows))] @@ -233,6 +237,7 @@ fn system_config_file() -> Option { { return Some(path); } + // Fallback to `/etc/uv/uv.toml` if `XDG_CONFIG_DIRS` is not set or no valid // path is found. let candidate = Path::new("/etc/uv/uv.toml"); @@ -262,17 +267,14 @@ pub enum Error { #[cfg(test)] mod test { + #[cfg(windows)] + use crate::locate_system_config_windows; #[cfg(not(windows))] use crate::locate_system_config_xdg; - #[cfg(windows)] - use crate::system_config_file; - #[cfg(windows)] - use uv_static::EnvVars; use assert_fs::fixture::FixtureError; use assert_fs::prelude::*; use indoc::indoc; - use std::env; #[test] #[cfg(not(windows))] @@ -286,22 +288,13 @@ mod test { "#, })?; - let current_dir = env::current_dir().unwrap(); + // None + assert_eq!(locate_system_config_xdg(None), None); - { - env::set_current_dir(context.path()).unwrap(); + // Empty string + assert_eq!(locate_system_config_xdg(Some("")), None); - // None - assert_eq!(locate_system_config_xdg(None), None); - - // Empty string - assert_eq!(locate_system_config_xdg(Some("")), None); - - // Single colon - assert_eq!(locate_system_config_xdg(Some(":")), None); - } - - env::set_current_dir(¤t_dir).unwrap(); + // Single colon assert_eq!(locate_system_config_xdg(Some(":")), None); // Assert that the `system_config_file` function returns the correct path. @@ -340,14 +333,10 @@ mod test { index-url = "https://test.pypi.org/simple" "#})?; - // Previous value of `SYSTEMDRIVE` which should always exist. - let prev_system_drive = env::var(EnvVars::SYSTEMDRIVE).unwrap_or("C:".to_string()); - // This is typically only a drive (that is, letter and colon) but we // allow anything, including a path to the test fixtures... - env::set_var(EnvVars::SYSTEMDRIVE, context.path().as_os_str()); assert_eq!( - system_config_file().unwrap(), + locate_system_config_windows(context.path().as_os_str()).unwrap(), context .child("ProgramData") .child("uv") @@ -357,10 +346,10 @@ mod test { // This does not have a `ProgramData` child, so contains no config. let context = assert_fs::TempDir::new()?; - env::set_var(EnvVars::SYSTEMDRIVE, context.path().as_os_str()); - assert_eq!(system_config_file(), None); - - env::set_var(EnvVars::SYSTEMDRIVE, prev_system_drive); + assert_eq!( + locate_system_config_windows(context.path().as_os_str()), + None + ); Ok(()) } diff --git a/docs/configuration/files.md b/docs/configuration/files.md index fbff28e7b2a7b..640321e6beb89 100644 --- a/docs/configuration/files.md +++ b/docs/configuration/files.md @@ -49,7 +49,9 @@ format, as a `pyproject.toml` is intended to define a Python _project_. If project-, user-, and system-level configuration files are found, the settings will be merged, with project-level configuration taking precedence over the user-level configuration, and user-level -configuration taking precedence over the system-level configuration. +configuration taking precedence over the system-level configuration. (If multiple system-level +configuration files are found, e.g., at both `/etc/uv/uv.toml` and `$XDG_CONFIG_DIRS/uv/uv.toml`, +only the first-discovered file will be used, with XDG taking priority.) For example, if a string, number, or boolean is present in both the project- and user-level configuration tables, the project-level value will be used, and the user-level value will be