diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 4c28e2506..2b1b3bce0 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2067,7 +2067,7 @@ pub enum ToolCommand { #[command(alias = "ensurepath")] UpdateShell, /// Show the tools directory. - Dir, + Dir(ToolDirArgs), } #[derive(Args)] @@ -2168,6 +2168,17 @@ pub struct ToolInstallArgs { #[allow(clippy::struct_excessive_bools)] pub struct ToolListArgs; +#[derive(Args)] +#[allow(clippy::struct_excessive_bools)] +pub struct ToolDirArgs { + /// Show the directory into which `uv tool` will install executables. + /// + /// 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, alias = "executable")] + pub bin: bool, +} + #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct ToolUninstallArgs { diff --git a/crates/uv/src/commands/tool/dir.rs b/crates/uv/src/commands/tool/dir.rs index 559379dd6..301bf7d52 100644 --- a/crates/uv/src/commands/tool/dir.rs +++ b/crates/uv/src/commands/tool/dir.rs @@ -3,16 +3,23 @@ use owo_colors::OwoColorize; use uv_configuration::PreviewMode; use uv_fs::Simplified; -use uv_tool::InstalledTools; +use uv_tool::{find_executable_directory, InstalledTools}; use uv_warnings::warn_user_once; /// Show the tool directory. -pub(crate) fn dir(preview: PreviewMode) -> anyhow::Result<()> { +pub(crate) fn dir(bin: bool, preview: PreviewMode) -> anyhow::Result<()> { if preview.is_disabled() { warn_user_once!("`uv tool dir` is experimental and may change without warning."); } - let installed_tools = - InstalledTools::from_settings().context("Failed to initialize tools settings")?; - anstream::println!("{}", installed_tools.root().simplified_display().cyan()); + + if bin { + let executable_directory = find_executable_directory()?; + anstream::println!("{}", executable_directory.simplified_display().cyan()); + } else { + let installed_tools = + InstalledTools::from_settings().context("Failed to initialize tools settings")?; + anstream::println!("{}", installed_tools.root().simplified_display().cyan()); + } + Ok(()) } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 6ba139046..a7ff37ee9 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -689,9 +689,13 @@ async fn run(cli: Cli) -> Result { Ok(ExitStatus::Success) } Commands::Tool(ToolNamespace { - command: ToolCommand::Dir, + command: ToolCommand::Dir(args), }) => { - commands::tool_dir(globals.preview)?; + // Resolve the settings from the command-line arguments and workspace configuration. + let args = settings::ToolDirSettings::resolve(args, filesystem); + show_settings!(args); + + commands::tool_dir(args.bin, globals.preview)?; Ok(ExitStatus::Success) } Commands::Python(PythonNamespace { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 8e6edbc66..fe5794fd8 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -14,8 +14,8 @@ use uv_cli::{ AddArgs, ColorChoice, Commands, ExternalCommand, GlobalArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, - PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs, - ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, + PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, + ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, }; use uv_client::Connectivity; use uv_configuration::{ @@ -316,6 +316,23 @@ impl ToolUninstallSettings { } } +/// The resolved settings to use for a `tool dir` invocation. +#[allow(clippy::struct_excessive_bools)] +#[derive(Debug, Clone)] +pub(crate) struct ToolDirSettings { + pub(crate) bin: bool, +} + +impl ToolDirSettings { + /// Resolve the [`ToolDirSettings`] from the CLI and filesystem configuration. + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn resolve(args: ToolDirArgs, _filesystem: Option) -> Self { + let ToolDirArgs { bin } = args; + + Self { bin } + } +} + #[derive(Debug, Clone, Default)] pub(crate) enum PythonListKinds { #[default] diff --git a/crates/uv/tests/tool_dir.rs b/crates/uv/tests/tool_dir.rs index f819cda07..cd59032e0 100644 --- a/crates/uv/tests/tool_dir.rs +++ b/crates/uv/tests/tool_dir.rs @@ -24,3 +24,22 @@ fn tool_dir() { warning: `uv tool dir` is experimental and may change without warning. "###); } + +#[test] +fn tool_dir_bin() { + let context = TestContext::new("3.12"); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + uv_snapshot!(context.filters(), context.tool_dir().arg("--bin") + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/bin + + ----- stderr ----- + warning: `uv tool dir` is experimental and may change without warning. + "###); +}