diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 16d8f871b..cfa91fb79 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2842,7 +2842,15 @@ pub struct ToolDirArgs { /// /// By default, `uv tool dir` shows the directory into which the tool Python environments /// themselves are installed, rather than the directory containing the linked executables. - #[arg(long)] + /// + /// The tool executable directory is determined according to the XDG standard and is derived + /// from the following environment variables, in order of preference: + /// + /// - `$UV_TOOL_BIN_DIR` + /// - `$XDG_BIN_HOME` + /// - `$XDG_DATA_HOME/../bin` + /// - `$HOME/.local/bin` + #[arg(long, verbatim_doc_comment)] pub bin: bool, } diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index e959523b3..fc6aef6c5 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -349,6 +349,7 @@ impl fmt::Display for InstalledTool { /// /// This follows, in order: /// +/// - `$UV_TOOL_BIN_DIR` /// - `$XDG_BIN_HOME` /// - `$XDG_DATA_HOME/../bin` /// - `$HOME/.local/bin` @@ -357,8 +358,9 @@ impl fmt::Display for InstalledTool { /// /// Errors if a directory cannot be found. pub fn find_executable_directory() -> Result { - std::env::var_os("XDG_BIN_HOME") + std::env::var_os("UV_TOOL_BIN_DIR") .and_then(dirs_sys::is_absolute_path) + .or_else(|| std::env::var_os("XDG_BIN_HOME").and_then(dirs_sys::is_absolute_path)) .or_else(|| { std::env::var_os("XDG_DATA_HOME") .and_then(dirs_sys::is_absolute_path) diff --git a/crates/uv/tests/tool_install.rs b/crates/uv/tests/tool_install.rs index a4a2d69d7..df92e4db7 100644 --- a/crates/uv/tests/tool_install.rs +++ b/crates/uv/tests/tool_install.rs @@ -1370,6 +1370,42 @@ fn tool_install_xdg_bin_home() { .assert(predicate::path::exists()); } +/// Test `uv tool install` when the bin directory is set by `$UV_TOOL_BIN_DIR` +#[test] +fn tool_install_tool_bin_dir() { + let context = TestContext::new("3.12").with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + // Install `black` + uv_snapshot!(context.filters(), context.tool_install() + .arg("black") + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("UV_TOOL_BIN_DIR", bin_dir.as_os_str()) + .env("PATH", bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv tool install` is experimental and may change without warning + Resolved 6 packages in [TIME] + Prepared 6 packages in [TIME] + Installed 6 packages in [TIME] + + black==24.3.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + Installed 2 executables: black, blackd + "###); + + bin_dir + .child(format!("black{}", std::env::consts::EXE_SUFFIX)) + .assert(predicate::path::exists()); +} + /// Test installing a tool that lacks entrypoints #[test] fn tool_install_no_entrypoints() { diff --git a/docs/concepts/tools.md b/docs/concepts/tools.md index cdc6a4226..5292322f1 100644 --- a/docs/concepts/tools.md +++ b/docs/concepts/tools.md @@ -122,6 +122,7 @@ notably including Windows and macOS — there is no clear alternative location t these platforms. The installation directory is determined from the first available environment variable: +- `$UV_TOOL_BIN_DIR` - `$XDG_BIN_HOME` - `$XDG_DATA_HOME/../bin` - `$HOME/.local/bin` diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md index a16296e62..e80f690ff 100644 --- a/docs/configuration/environment.md +++ b/docs/configuration/environment.md @@ -68,6 +68,7 @@ In addition, uv respects the following environment variables: - `UV_CONCURRENT_INSTALLS`: Used to control the number of threads used when installing and unzipping packages. - `UV_TOOL_DIR`: Used to specify the directory where uv will store managed tools. +- `UV_TOOL_BIN_DIR`: Used to specify the "bin" directory where uv will install tool executables. - `UV_PYTHON_INSTALL_DIR`: Used to specify the directory where uv will store managed Python installations. - `UV_PYTHON_INSTALL_MIRROR`: Managed Python installations are downloaded from diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a6c1e9d90..36da9eac7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2769,6 +2769,18 @@ uv tool dir [OPTIONS]

By default, uv tool dir shows the directory into which the tool Python environments themselves are installed, rather than the directory containing the linked executables.

+

The tool executable directory is determined according to the XDG standard and is derived from the following environment variables, in order of preference:

+ + +
--cache-dir cache-dir

Path to the cache directory.

Defaults to $HOME/Library/Caches/uv on macOS, $XDG_CACHE_HOME/uv or $HOME/.cache/uv on Linux, and {FOLDERID_LocalAppData}\uv\cache on Windows.