mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Move config dir functions to public functions in uv_dirs (#12090)
This PR moves functions for finding user- and system-level config directories to public functions in `uv_fs::config`. This will allow them to be used in future work without duplicating code.
This commit is contained in:
parent
9776dc5882
commit
ba74b9ea93
6 changed files with 200 additions and 190 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -4928,7 +4928,11 @@ dependencies = [
|
|||
name = "uv-dirs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"assert_fs",
|
||||
"etcetera",
|
||||
"fs-err 3.1.0",
|
||||
"indoc",
|
||||
"tracing",
|
||||
"uv-static",
|
||||
]
|
||||
|
||||
|
@ -5614,11 +5618,8 @@ dependencies = [
|
|||
name = "uv-settings"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"assert_fs",
|
||||
"clap",
|
||||
"etcetera",
|
||||
"fs-err 3.1.0",
|
||||
"indoc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"textwrap",
|
||||
|
@ -5628,6 +5629,7 @@ dependencies = [
|
|||
"url",
|
||||
"uv-cache-info",
|
||||
"uv-configuration",
|
||||
"uv-dirs",
|
||||
"uv-distribution-types",
|
||||
"uv-fs",
|
||||
"uv-install-wheel",
|
||||
|
|
|
@ -20,3 +20,9 @@ workspace = true
|
|||
uv-static = { workspace = true }
|
||||
|
||||
etcetera = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_fs = { version = "1.1.2" }
|
||||
indoc = { workspace = true }
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use std::{ffi::OsString, path::PathBuf};
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use etcetera::BaseStrategy;
|
||||
|
||||
|
@ -88,3 +92,184 @@ fn parse_path(path: OsString) -> Option<PathBuf> {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the user configuration directory.
|
||||
///
|
||||
/// On Windows, use, e.g., C:\Users\Alice\AppData\Roaming
|
||||
/// On Linux and macOS, use `XDG_CONFIG_HOME` or $HOME/.config, e.g., /home/alice/.config.
|
||||
pub fn user_config_dir() -> Option<PathBuf> {
|
||||
etcetera::choose_base_strategy()
|
||||
.map(|dirs| dirs.config_dir())
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn locate_system_config_xdg(value: Option<&str>) -> Option<PathBuf> {
|
||||
// On Linux and macOS, read the `XDG_CONFIG_DIRS` environment variable.
|
||||
|
||||
use std::path::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
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn locate_system_config_windows(system_drive: impl AsRef<Path>) -> Option<PathBuf> {
|
||||
// On Windows, use `%SYSTEMDRIVE%\ProgramData\uv\uv.toml` (e.g., `C:\ProgramData`).
|
||||
let candidate = system_drive
|
||||
.as_ref()
|
||||
.join("ProgramData")
|
||||
.join("uv")
|
||||
.join("uv.toml");
|
||||
candidate.as_path().is_file().then_some(candidate)
|
||||
}
|
||||
|
||||
/// Returns the path to the system configuration file.
|
||||
///
|
||||
/// 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`.
|
||||
pub fn system_config_file() -> Option<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
env::var(EnvVars::SYSTEMDRIVE)
|
||||
.ok()
|
||||
.and_then(|system_drive| locate_system_config_windows(format!("{system_drive}\\")))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if let Some(path) =
|
||||
locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref())
|
||||
{
|
||||
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");
|
||||
match candidate.try_exists() {
|
||||
Ok(true) => Some(candidate.to_path_buf()),
|
||||
Ok(false) => None,
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to query system configuration file: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(windows)]
|
||||
use crate::locate_system_config_windows;
|
||||
#[cfg(not(windows))]
|
||||
use crate::locate_system_config_xdg;
|
||||
|
||||
use assert_fs::fixture::FixtureError;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
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"
|
||||
"#,
|
||||
})?;
|
||||
|
||||
// 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);
|
||||
|
||||
// Assert that the `system_config_file` function returns the correct path.
|
||||
assert_eq!(
|
||||
locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(),
|
||||
context.child("uv").child("uv.toml").path()
|
||||
);
|
||||
|
||||
// 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.to_string_lossy(), context.to_string_lossy()).as_str()
|
||||
))
|
||||
.unwrap(),
|
||||
first_config.path()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_locate_system_config_xdg_unix_permissions() -> Result<(), FixtureError> {
|
||||
let context = assert_fs::TempDir::new()?;
|
||||
let config = context.child("uv").child("uv.toml");
|
||||
config.write_str("")?;
|
||||
fs_err::set_permissions(
|
||||
&context,
|
||||
std::os::unix::fs::PermissionsExt::from_mode(0o000),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
locate_system_config_xdg(Some(context.to_str().unwrap())),
|
||||
None
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
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"
|
||||
"#})?;
|
||||
|
||||
// This is typically only a drive (that is, letter and colon) but we
|
||||
// allow anything, including a path to the test fixtures...
|
||||
assert_eq!(
|
||||
locate_system_config_windows(context.path()).unwrap(),
|
||||
context
|
||||
.child("ProgramData")
|
||||
.child("uv")
|
||||
.child("uv.toml")
|
||||
.path()
|
||||
);
|
||||
|
||||
// This does not have a `ProgramData` child, so contains no config.
|
||||
let context = assert_fs::TempDir::new()?;
|
||||
assert_eq!(locate_system_config_windows(context.path()), None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
dunce = { workspace = true }
|
||||
either = { workspace = true }
|
||||
encoding_rs_io = { workspace = true }
|
||||
|
|
|
@ -18,6 +18,7 @@ workspace = true
|
|||
[dependencies]
|
||||
uv-cache-info = { workspace = true, features = ["schemars"] }
|
||||
uv-configuration = { workspace = true, features = ["schemars", "clap"] }
|
||||
uv-dirs = { workspace = true }
|
||||
uv-distribution-types = { workspace = true, features = ["schemars"] }
|
||||
uv-fs = { workspace = true }
|
||||
uv-install-wheel = { workspace = true, features = ["schemars", "clap"] }
|
||||
|
@ -32,7 +33,6 @@ uv-static = { workspace = true }
|
|||
uv-warnings = { workspace = true }
|
||||
|
||||
clap = { workspace = true }
|
||||
etcetera = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
|
@ -46,5 +46,3 @@ url = { workspace = true }
|
|||
ignored = ["uv-options-metadata", "clap"]
|
||||
|
||||
[dev-dependencies]
|
||||
assert_fs = { version = "1.1.2" }
|
||||
indoc = { workspace = true }
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use std::env;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use etcetera::BaseStrategy;
|
||||
|
||||
use uv_dirs::{system_config_file, user_config_dir};
|
||||
use uv_fs::Simplified;
|
||||
use uv_static::EnvVars;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
pub use crate::combine::*;
|
||||
|
@ -180,78 +177,6 @@ impl From<Options> for FilesystemOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the user configuration directory.
|
||||
///
|
||||
/// On Windows, use, e.g., C:\Users\Alice\AppData\Roaming
|
||||
/// On Linux and macOS, use `XDG_CONFIG_HOME` or $HOME/.config, e.g., /home/alice/.config.
|
||||
fn user_config_dir() -> Option<PathBuf> {
|
||||
etcetera::choose_base_strategy()
|
||||
.map(|dirs| dirs.config_dir())
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn locate_system_config_xdg(value: Option<&str>) -> Option<PathBuf> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn locate_system_config_windows(system_drive: impl AsRef<Path>) -> Option<PathBuf> {
|
||||
// On Windows, use `%SYSTEMDRIVE%\ProgramData\uv\uv.toml` (e.g., `C:\ProgramData`).
|
||||
let candidate = system_drive
|
||||
.as_ref()
|
||||
.join("ProgramData")
|
||||
.join("uv")
|
||||
.join("uv.toml");
|
||||
candidate.as_path().is_file().then_some(candidate)
|
||||
}
|
||||
|
||||
/// Returns the path to the system configuration file.
|
||||
///
|
||||
/// 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<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
env::var(EnvVars::SYSTEMDRIVE)
|
||||
.ok()
|
||||
.and_then(|system_drive| locate_system_config_windows(format!("{system_drive}\\")))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if let Some(path) =
|
||||
locate_system_config_xdg(env::var(EnvVars::XDG_CONFIG_DIRS).ok().as_deref())
|
||||
{
|
||||
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");
|
||||
match candidate.try_exists() {
|
||||
Ok(true) => Some(candidate.to_path_buf()),
|
||||
Ok(false) => None,
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to query system configuration file: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`Options`] from a `uv.toml` file.
|
||||
fn read_file(path: &Path) -> Result<Options, Error> {
|
||||
let content = fs_err::read_to_string(path)?;
|
||||
|
@ -314,110 +239,3 @@ pub enum Error {
|
|||
#[error("Failed to parse: `{}`. The `{}` field is not allowed in a `uv.toml` file. `{}` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead.", _0.user_display(), _1, _1)]
|
||||
PyprojectOnlyField(PathBuf, &'static str),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(windows)]
|
||||
use crate::locate_system_config_windows;
|
||||
#[cfg(not(windows))]
|
||||
use crate::locate_system_config_xdg;
|
||||
|
||||
use assert_fs::fixture::FixtureError;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
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"
|
||||
"#,
|
||||
})?;
|
||||
|
||||
// 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);
|
||||
|
||||
// Assert that the `system_config_file` function returns the correct path.
|
||||
assert_eq!(
|
||||
locate_system_config_xdg(Some(context.to_str().unwrap())).unwrap(),
|
||||
context.child("uv").child("uv.toml").path()
|
||||
);
|
||||
|
||||
// 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.to_string_lossy(), context.to_string_lossy()).as_str()
|
||||
))
|
||||
.unwrap(),
|
||||
first_config.path()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_locate_system_config_xdg_unix_permissions() -> Result<(), FixtureError> {
|
||||
let context = assert_fs::TempDir::new()?;
|
||||
let config = context.child("uv").child("uv.toml");
|
||||
config.write_str("")?;
|
||||
fs_err::set_permissions(
|
||||
&context,
|
||||
std::os::unix::fs::PermissionsExt::from_mode(0o000),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
locate_system_config_xdg(Some(context.to_str().unwrap())),
|
||||
None
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
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"
|
||||
"#})?;
|
||||
|
||||
// This is typically only a drive (that is, letter and colon) but we
|
||||
// allow anything, including a path to the test fixtures...
|
||||
assert_eq!(
|
||||
locate_system_config_windows(context.path()).unwrap(),
|
||||
context
|
||||
.child("ProgramData")
|
||||
.child("uv")
|
||||
.child("uv.toml")
|
||||
.path()
|
||||
);
|
||||
|
||||
// This does not have a `ProgramData` child, so contains no config.
|
||||
let context = assert_fs::TempDir::new()?;
|
||||
assert_eq!(locate_system_config_windows(context.path()), None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue