Add uv-dirs to consolidate directory lookup methods (#8453)
I need the executable directory outside `uv-tool` and figured I should consolidate these to a central location.
This commit is contained in:
Generated
+13
-5
@@ -4335,8 +4335,6 @@ name = "uv-cache"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"directories",
|
||||
"etcetera",
|
||||
"fs-err",
|
||||
"nanoid",
|
||||
"rmp-serde",
|
||||
@@ -4347,6 +4345,7 @@ dependencies = [
|
||||
"url",
|
||||
"uv-cache-info",
|
||||
"uv-cache-key",
|
||||
"uv-dirs",
|
||||
"uv-distribution-types",
|
||||
"uv-fs",
|
||||
"uv-normalize",
|
||||
@@ -4533,6 +4532,16 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uv-dirs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"dirs-sys",
|
||||
"etcetera",
|
||||
"uv-static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uv-dispatch"
|
||||
version = "0.0.1"
|
||||
@@ -5212,10 +5221,9 @@ dependencies = [
|
||||
name = "uv-state"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"etcetera",
|
||||
"fs-err",
|
||||
"tempfile",
|
||||
"uv-dirs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5226,7 +5234,6 @@ version = "0.0.1"
|
||||
name = "uv-tool"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"fs-err",
|
||||
"pathdiff",
|
||||
"serde",
|
||||
@@ -5235,6 +5242,7 @@ dependencies = [
|
||||
"toml_edit",
|
||||
"tracing",
|
||||
"uv-cache",
|
||||
"uv-dirs",
|
||||
"uv-fs",
|
||||
"uv-install-wheel",
|
||||
"uv-installer",
|
||||
|
||||
@@ -31,6 +31,7 @@ uv-cli = { path = "crates/uv-cli" }
|
||||
uv-client = { path = "crates/uv-client" }
|
||||
uv-configuration = { path = "crates/uv-configuration" }
|
||||
uv-console = { path = "crates/uv-console" }
|
||||
uv-dirs = { path = "crates/uv-dirs" }
|
||||
uv-dispatch = { path = "crates/uv-dispatch" }
|
||||
uv-distribution = { path = "crates/uv-distribution" }
|
||||
uv-distribution-filename = { path = "crates/uv-distribution-filename" }
|
||||
|
||||
@@ -17,6 +17,7 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-dirs = { workspace = true }
|
||||
uv-cache-info = { workspace = true }
|
||||
uv-cache-key = { workspace = true }
|
||||
uv-distribution-types = { workspace = true }
|
||||
@@ -26,8 +27,6 @@ uv-pypi-types = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
|
||||
clap = { workspace = true, features = ["derive", "env"], optional = true }
|
||||
directories = { workspace = true }
|
||||
etcetera = { workspace = true }
|
||||
fs-err = { workspace = true, features = ["tokio"] }
|
||||
nanoid = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
|
||||
@@ -4,8 +4,6 @@ use uv_static::EnvVars;
|
||||
|
||||
use crate::Cache;
|
||||
use clap::Parser;
|
||||
use directories::ProjectDirs;
|
||||
use etcetera::BaseStrategy;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
@@ -45,18 +43,13 @@ impl Cache {
|
||||
Self::temp()
|
||||
} else if let Some(cache_dir) = cache_dir {
|
||||
Ok(Self::from_path(cache_dir))
|
||||
} else if let Some(cache_dir) = ProjectDirs::from("", "", "uv")
|
||||
.map(|dirs| dirs.cache_dir().to_path_buf())
|
||||
.filter(|dir| dir.exists())
|
||||
} else if let Some(cache_dir) = uv_dirs::legacy_user_cache_dir().filter(|dir| dir.exists())
|
||||
{
|
||||
// If the user has an existing directory at (e.g.) `/Users/user/Library/Caches/uv`,
|
||||
// respect it for backwards compatibility. Otherwise, prefer the XDG strategy, even on
|
||||
// macOS.
|
||||
Ok(Self::from_path(cache_dir))
|
||||
} else if let Some(cache_dir) = etcetera::base_strategy::choose_base_strategy()
|
||||
.ok()
|
||||
.map(|dirs| dirs.cache_dir().join("uv"))
|
||||
{
|
||||
} else if let Some(cache_dir) = uv_dirs::user_cache_dir() {
|
||||
if cfg!(windows) {
|
||||
// On Windows, we append `cache` to the LocalAppData directory, i.e., prefer
|
||||
// `C:\Users\User\AppData\Local\uv\cache` over `C:\Users\User\AppData\Local\uv`.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "uv-dirs"
|
||||
version = "0.0.1"
|
||||
description = "Resolution of directories for storage of uv state"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-static = { workspace = true }
|
||||
|
||||
dirs-sys = { workspace = true }
|
||||
directories = { workspace = true }
|
||||
etcetera = { workspace = true }
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use etcetera::BaseStrategy;
|
||||
|
||||
use uv_static::EnvVars;
|
||||
|
||||
/// Returns an appropriate user-level directory for storing executables.
|
||||
///
|
||||
/// This follows, in order:
|
||||
///
|
||||
/// - `$OVERRIDE_VARIABLE` (if provided)
|
||||
/// - `$XDG_BIN_HOME`
|
||||
/// - `$XDG_DATA_HOME/../bin`
|
||||
/// - `$HOME/.local/bin`
|
||||
///
|
||||
/// On all platforms.
|
||||
///
|
||||
/// Returns `None` if a directory cannot be found, i.e., if `$HOME` cannot be resolved. Does not
|
||||
/// check if the directory exists.
|
||||
pub fn user_executable_directory(override_variable: Option<&'static str>) -> Option<PathBuf> {
|
||||
override_variable
|
||||
.and_then(std::env::var_os)
|
||||
.and_then(dirs_sys::is_absolute_path)
|
||||
.or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(dirs_sys::is_absolute_path))
|
||||
.or_else(|| {
|
||||
std::env::var_os(EnvVars::XDG_DATA_HOME)
|
||||
.and_then(dirs_sys::is_absolute_path)
|
||||
.map(|path| path.join("../bin"))
|
||||
})
|
||||
.or_else(|| {
|
||||
// See https://github.com/dirs-dev/dirs-rs/blob/50b50f31f3363b7656e5e63b3fa1060217cbc844/src/win.rs#L5C58-L5C78
|
||||
#[cfg(windows)]
|
||||
let home_dir = dirs_sys::known_folder_profile();
|
||||
#[cfg(not(windows))]
|
||||
let home_dir = dirs_sys::home_dir();
|
||||
home_dir.map(|path| path.join(".local").join("bin"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an appropriate user-level directory for storing the cache.
|
||||
///
|
||||
/// Corresponds to `$XDG_CACHE_HOME/uv` on Unix.
|
||||
pub fn user_cache_dir() -> Option<PathBuf> {
|
||||
etcetera::base_strategy::choose_base_strategy()
|
||||
.ok()
|
||||
.map(|dirs| dirs.cache_dir().join("uv"))
|
||||
}
|
||||
|
||||
/// Returns the legacy cache directory path.
|
||||
///
|
||||
/// Uses `/Users/user/Library/Application Support/uv` on macOS, in contrast to the new preference
|
||||
/// for using the XDG directories on all Unix platforms.
|
||||
pub fn legacy_user_cache_dir() -> Option<PathBuf> {
|
||||
directories::ProjectDirs::from("", "", "uv").map(|dirs| dirs.cache_dir().to_path_buf())
|
||||
}
|
||||
|
||||
/// Returns an appropriate user-level directory for storing application state.
|
||||
///
|
||||
/// Corresponds to `$XDG_DATA_HOME/uv` on Unix.
|
||||
pub fn user_state_dir() -> Option<PathBuf> {
|
||||
etcetera::base_strategy::choose_base_strategy()
|
||||
.ok()
|
||||
.map(|dirs| dirs.data_dir().join("uv"))
|
||||
}
|
||||
|
||||
/// Returns the legacy state directory path.
|
||||
///
|
||||
/// Uses `/Users/user/Library/Application Support/uv` on macOS, in contrast to the new preference
|
||||
/// for using the XDG directories on all Unix platforms.
|
||||
pub fn legacy_user_state_dir() -> Option<PathBuf> {
|
||||
directories::ProjectDirs::from("", "", "uv").map(|dirs| dirs.data_dir().to_path_buf())
|
||||
}
|
||||
@@ -16,7 +16,6 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
directories = { workspace = true }
|
||||
etcetera = { workspace = true }
|
||||
uv-dirs = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
|
||||
@@ -4,14 +4,13 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use etcetera::BaseStrategy;
|
||||
use fs_err as fs;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
/// The main state storage abstraction.
|
||||
///
|
||||
/// This is appropriate
|
||||
/// This is appropriate for storing persistent data that is not user-facing, such as managed Python
|
||||
/// installations or tool environments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StateStore {
|
||||
/// The state storage.
|
||||
@@ -85,18 +84,12 @@ impl StateStore {
|
||||
pub fn from_settings(state_dir: Option<PathBuf>) -> Result<Self, io::Error> {
|
||||
if let Some(state_dir) = state_dir {
|
||||
StateStore::from_path(state_dir)
|
||||
} else if let Some(data_dir) = ProjectDirs::from("", "", "uv")
|
||||
.map(|dirs| dirs.data_dir().to_path_buf())
|
||||
.filter(|dir| dir.exists())
|
||||
{
|
||||
} else if let Some(data_dir) = uv_dirs::legacy_user_state_dir().filter(|dir| dir.exists()) {
|
||||
// If the user has an existing directory at (e.g.) `/Users/user/Library/Application Support/uv`,
|
||||
// respect it for backwards compatibility. Otherwise, prefer the XDG strategy, even on
|
||||
// macOS.
|
||||
StateStore::from_path(data_dir)
|
||||
} else if let Some(data_dir) = etcetera::base_strategy::choose_base_strategy()
|
||||
.ok()
|
||||
.map(|dirs| dirs.data_dir().join("uv"))
|
||||
{
|
||||
} else if let Some(data_dir) = uv_dirs::user_state_dir() {
|
||||
StateStore::from_path(data_dir)
|
||||
} else {
|
||||
StateStore::from_path(".uv")
|
||||
|
||||
@@ -17,6 +17,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-cache = { workspace = true }
|
||||
uv-dirs = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-install-wheel = { workspace = true }
|
||||
uv-installer = { workspace = true }
|
||||
@@ -28,8 +29,6 @@ uv-settings = { workspace = true }
|
||||
uv-state = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
|
||||
dirs-sys = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
pathdiff = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -2,6 +2,7 @@ use core::fmt;
|
||||
|
||||
use fs_err as fs;
|
||||
|
||||
use uv_dirs::user_executable_directory;
|
||||
use uv_pep440::Version;
|
||||
use uv_pep508::{InvalidNameError, PackageName};
|
||||
|
||||
@@ -354,36 +355,9 @@ impl fmt::Display for InstalledTool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a directory to place executables in.
|
||||
///
|
||||
/// This follows, in order:
|
||||
///
|
||||
/// - `$UV_TOOL_BIN_DIR`
|
||||
/// - `$XDG_BIN_HOME`
|
||||
/// - `$XDG_DATA_HOME/../bin`
|
||||
/// - `$HOME/.local/bin`
|
||||
///
|
||||
/// On all platforms.
|
||||
///
|
||||
/// Errors if a directory cannot be found.
|
||||
pub fn find_executable_directory() -> Result<PathBuf, Error> {
|
||||
std::env::var_os(EnvVars::UV_TOOL_BIN_DIR)
|
||||
.and_then(dirs_sys::is_absolute_path)
|
||||
.or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(dirs_sys::is_absolute_path))
|
||||
.or_else(|| {
|
||||
std::env::var_os(EnvVars::XDG_DATA_HOME)
|
||||
.and_then(dirs_sys::is_absolute_path)
|
||||
.map(|path| path.join("../bin"))
|
||||
})
|
||||
.or_else(|| {
|
||||
// See https://github.com/dirs-dev/dirs-rs/blob/50b50f31f3363b7656e5e63b3fa1060217cbc844/src/win.rs#L5C58-L5C78
|
||||
#[cfg(windows)]
|
||||
let home_dir = dirs_sys::known_folder_profile();
|
||||
#[cfg(not(windows))]
|
||||
let home_dir = dirs_sys::home_dir();
|
||||
home_dir.map(|path| path.join(".local").join("bin"))
|
||||
})
|
||||
.ok_or(Error::NoExecutableDirectory)
|
||||
/// Find the tool executable directory.
|
||||
pub fn tool_executable_dir() -> Result<PathBuf, Error> {
|
||||
user_executable_directory(Some(EnvVars::UV_TOOL_BIN_DIR)).ok_or(Error::NoExecutableDirectory)
|
||||
}
|
||||
|
||||
/// Find the `.dist-info` directory for a package in an environment.
|
||||
|
||||
@@ -16,7 +16,7 @@ use uv_pypi_types::Requirement;
|
||||
use uv_python::PythonEnvironment;
|
||||
use uv_settings::ToolOptions;
|
||||
use uv_shell::Shell;
|
||||
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
||||
use uv_tool::{entrypoint_paths, tool_executable_dir, InstalledTools, Tool, ToolEntrypoint};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
@@ -79,7 +79,7 @@ pub(crate) fn install_executables(
|
||||
};
|
||||
|
||||
// Find a suitable path to install into
|
||||
let executable_directory = find_executable_directory()?;
|
||||
let executable_directory = tool_executable_dir()?;
|
||||
fs_err::create_dir_all(&executable_directory)
|
||||
.context("Failed to create executable directory")?;
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ use owo_colors::OwoColorize;
|
||||
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_fs::Simplified;
|
||||
use uv_tool::{find_executable_directory, InstalledTools};
|
||||
use uv_tool::{tool_executable_dir, InstalledTools};
|
||||
|
||||
/// Show the tool directory.
|
||||
pub(crate) fn dir(bin: bool, _preview: PreviewMode) -> anyhow::Result<()> {
|
||||
if bin {
|
||||
let executable_directory = find_executable_directory()?;
|
||||
let executable_directory = tool_executable_dir()?;
|
||||
println!("{}", executable_directory.simplified_display().cyan());
|
||||
} else {
|
||||
let installed_tools =
|
||||
|
||||
@@ -9,14 +9,14 @@ use tracing::debug;
|
||||
|
||||
use uv_fs::Simplified;
|
||||
use uv_shell::Shell;
|
||||
use uv_tool::find_executable_directory;
|
||||
use uv_tool::tool_executable_dir;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
/// Ensure that the executable directory is in PATH.
|
||||
pub(crate) async fn update_shell(printer: Printer) -> Result<ExitStatus> {
|
||||
let executable_directory = find_executable_directory()?;
|
||||
let executable_directory = tool_executable_dir()?;
|
||||
debug!(
|
||||
"Ensuring that the executable directory is in PATH: {}",
|
||||
executable_directory.simplified_display()
|
||||
|
||||
Reference in New Issue
Block a user