diff --git a/Cargo.lock b/Cargo.lock index c85c71f625292..e831ae58ef507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5026,6 +5026,7 @@ dependencies = [ name = "uv-workspace" version = "0.0.1" dependencies = [ + "dirs-sys", "distribution-types", "fs-err", "install-wheel-rs", diff --git a/Cargo.toml b/Cargo.toml index 44ebea813b159..b709bc6387282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ dashmap = { version = "5.5.3" } data-encoding = { version = "2.5.0" } derivative = { version = "2.2.0" } directories = { version = "5.0.1" } +dirs-sys = { version = "0.4.1" } dunce = { version = "1.0.4" } either = { version = "1.9.0" } encoding_rs_io = { version = "0.1.7" } diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index cf57d33ebf8e7..f0c62c2e656fe 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -24,6 +24,7 @@ uv-resolver = { workspace = true, features = ["schemars", "serde"] } uv-toolchain = { workspace = true, features = ["schemars", "serde"] } uv-warnings = { workspace = true } +dirs-sys = { workspace = true } fs-err = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true } diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index ee1ee3889c6e0..5e2ed9fa64f36 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -15,6 +15,19 @@ pub struct Workspace { } impl Workspace { + /// Load the user [`Workspace`]. + pub fn user() -> Result, WorkspaceError> { + let Some(dir) = config_dir() else { + return Ok(None); + }; + let root = dir.join("uv"); + let file = root.join("uv.toml"); + Ok(Some(Self { + options: find_in_directory(&file)?.unwrap_or_default(), + root, + })) + } + /// Find the [`Workspace`] for the given path. /// /// The search starts at the given path and goes up the directory tree until a workspace is @@ -53,6 +66,27 @@ impl Workspace { } } +/// Returns the path to the user configuration directory. +/// +/// 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 { + // 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. + #[cfg(not(windows))] + { + std::env::var_os("XDG_CONFIG_HOME") + .and_then(dirs_sys::is_absolute_path) + .or_else(|| dirs_sys::home_dir().map(|path| path.join(".config"))) + } +} + /// Read a `uv.toml` or `pyproject.toml` file in the given directory. fn find_in_directory(dir: &Path) -> Result, WorkspaceError> { // Read a `uv.toml` file in the current directory. diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index ca4263551a99a..1eeaf1efd0523 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -109,11 +109,16 @@ async fn run() -> Result { } }; - // Load the workspace settings. + // Load the workspace settings, prioritizing (in order): + // 1. The configuration file specified on the command-line. + // 2. The configuration file in the current directory. + // 3. The user configuration file. let workspace = if let Some(config_file) = cli.config_file.as_ref() { Some(uv_workspace::Workspace::from_file(config_file)?) + } else if let Some(workspace) = uv_workspace::Workspace::find(env::current_dir()?)? { + Some(workspace) } else { - uv_workspace::Workspace::find(env::current_dir()?)? + uv_workspace::Workspace::user()? }; // Resolve the global settings.