diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index b4b0a176a47b..7954bf92b13b 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3559,6 +3559,11 @@ pub struct PythonListArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct PythonInstallArgs { + /// The directory where Python will be installed. + /// + #[arg(long, short, env = "UV_PYTHON_INSTALL_DIR")] + pub install_dir: Option, + /// The Python version(s) to install. /// /// If not provided, the requested Python version(s) will be read from the @@ -3580,6 +3585,11 @@ pub struct PythonInstallArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct PythonUninstallArgs { + /// The directory where Python is installed. + /// + #[arg(long, short, env = "UV_PYTHON_INSTALL_DIR")] + pub install_dir: Option, + /// The Python version(s) to uninstall. /// /// See `uv help python` to view supported request formats. diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 7f5d538ff454..28da2202eac9 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -65,7 +65,7 @@ pub struct ManagedPythonInstallations { impl ManagedPythonInstallations { /// A directory for Python installations at `root`. - fn from_path(root: impl Into) -> Self { + pub fn from_path(root: impl Into) -> Self { Self { root: root.into() } } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index ac373eafd7c1..bc93be6057bd 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -21,6 +21,7 @@ use crate::printer::Printer; /// Download and install Python versions. pub(crate) async fn install( project_dir: &Path, + install_dir: Option<&Path>, targets: Vec, reinstall: bool, python_downloads: PythonDownloads, @@ -31,7 +32,12 @@ pub(crate) async fn install( ) -> Result { let start = std::time::Instant::now(); - let installations = ManagedPythonInstallations::from_settings()?.init()?; + let installations = if let Some(install_dir) = install_dir { + ManagedPythonInstallations::from_path(install_dir) + } else { + ManagedPythonInstallations::from_settings()? + } + .init()?; let installations_dir = installations.root(); let cache_dir = installations.cache(); let _lock = installations.lock().await?; diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 11cd6602c520..2d961d1f52d3 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -14,15 +14,22 @@ use uv_python::PythonRequest; use crate::commands::python::{ChangeEvent, ChangeEventKind}; use crate::commands::{elapsed, ExitStatus}; use crate::printer::Printer; +use std::path::Path; /// Uninstall managed Python versions. pub(crate) async fn uninstall( + install_dir: Option<&Path>, targets: Vec, all: bool, printer: Printer, ) -> Result { - let installations = ManagedPythonInstallations::from_settings()?.init()?; + let installations = if let Some(install_dir) = install_dir { + ManagedPythonInstallations::from_path(install_dir) + } else { + ManagedPythonInstallations::from_settings()?.init()? + }; + let _lock = installations.lock().await?; // Perform the uninstallation. diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 4e236d470ebd..7faf6e91ab20 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1016,6 +1016,7 @@ async fn run(cli: Cli) -> Result { commands::python_install( &project_dir, + args.install_dir.as_deref(), args.targets, args.reinstall, globals.python_downloads, @@ -1033,7 +1034,8 @@ async fn run(cli: Cli) -> Result { let args = settings::PythonUninstallSettings::resolve(args, filesystem); show_settings!(args); - commands::python_uninstall(args.targets, args.all, printer).await + commands::python_uninstall(args.install_dir.as_deref(), args.targets, args.all, printer) + .await } Commands::Python(PythonNamespace { command: PythonCommand::Find(args), diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 582e20787cc6..c8b53b19bc65 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -585,6 +585,7 @@ impl PythonListSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonInstallSettings { + pub(crate) install_dir: Option, pub(crate) targets: Vec, pub(crate) reinstall: bool, } @@ -593,9 +594,17 @@ impl PythonInstallSettings { /// Resolve the [`PythonInstallSettings`] from the CLI and filesystem configuration. #[allow(clippy::needless_pass_by_value)] pub(crate) fn resolve(args: PythonInstallArgs, _filesystem: Option) -> Self { - let PythonInstallArgs { targets, reinstall } = args; + let PythonInstallArgs { + install_dir, + targets, + reinstall, + } = args; - Self { targets, reinstall } + Self { + install_dir, + targets, + reinstall, + } } } @@ -603,6 +612,7 @@ impl PythonInstallSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PythonUninstallSettings { + pub(crate) install_dir: Option, pub(crate) targets: Vec, pub(crate) all: bool, } @@ -614,9 +624,17 @@ impl PythonUninstallSettings { args: PythonUninstallArgs, _filesystem: Option, ) -> Self { - let PythonUninstallArgs { targets, all } = args; + let PythonUninstallArgs { + install_dir, + targets, + all, + } = args; - Self { targets, all } + Self { + install_dir, + targets, + all, + } } }