diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index badb1e522993..658a7ad553ca 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -4250,6 +4250,16 @@ pub struct PythonDirArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct PythonInstallArgs { + /// The directory to store the Python installation in. + /// + /// If provided, `UV_PYTHON_INSTALL_DIR` will need to be set for subsequent operations for + /// uv to discover the Python installation. + /// + /// See `uv python dir` to view the current Python installation directory. Defaults to + /// `~/.local/share/uv/python`. + #[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 @@ -4310,6 +4320,10 @@ pub struct PythonInstallArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct PythonUninstallArgs { + /// The directory where the Python was 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/discovery.rs b/crates/uv-python/src/discovery.rs index 52fae3b5f69d..c2e1f48bf35e 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -302,7 +302,7 @@ fn python_executables_from_installed<'a>( preference: PythonPreference, ) -> Box> + 'a> { let from_managed_installations = std::iter::once_with(move || { - ManagedPythonInstallations::from_settings() + ManagedPythonInstallations::from_settings(None) .map_err(Error::from) .and_then(|installed_installations| { debug!( diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 2b3b11ae527e..6ab242296957 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -135,7 +135,7 @@ impl PythonInstallation { python_install_mirror: Option<&str>, pypy_install_mirror: Option<&str>, ) -> Result { - let installations = ManagedPythonInstallations::from_settings()?.init()?; + let installations = ManagedPythonInstallations::from_settings(None)?.init()?; let installations_dir = installations.root(); let scratch_dir = installations.scratch(); let _lock = installations.lock().await?; diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index b7e9dc253159..a282cbda6a1e 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -107,11 +107,15 @@ impl ManagedPythonInstallations { } /// Prefer, in order: - /// 1. The specific Python directory specified by the user, i.e., `UV_PYTHON_INSTALL_DIR` - /// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/python` - /// 3. A directory in the local data directory, e.g., `./.uv/python` - pub fn from_settings() -> Result { - if let Some(install_dir) = std::env::var_os(EnvVars::UV_PYTHON_INSTALL_DIR) { + /// + /// 1. The specific Python directory passed via the `install_dir` argument. + /// 2. The specific Python directory specified with the `UV_PYTHON_INSTALL_DIR` environment variable. + /// 3. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/python`. + /// 4. A directory in the local data directory, e.g., `./.uv/python`. + pub fn from_settings(install_dir: Option) -> Result { + if let Some(install_dir) = install_dir { + Ok(Self::from_path(install_dir)) + } else if let Some(install_dir) = std::env::var_os(EnvVars::UV_PYTHON_INSTALL_DIR) { Ok(Self::from_path(install_dir)) } else { Ok(Self::from_path( @@ -227,7 +231,7 @@ impl ManagedPythonInstallations { ) -> Result, Error> { let platform_key = platform_key_from_env()?; - let iter = ManagedPythonInstallations::from_settings()? + let iter = ManagedPythonInstallations::from_settings(None)? .find_all()? .filter(move |installation| { installation diff --git a/crates/uv/src/commands/python/dir.rs b/crates/uv/src/commands/python/dir.rs index 7c6a1b325726..27f469d9af68 100644 --- a/crates/uv/src/commands/python/dir.rs +++ b/crates/uv/src/commands/python/dir.rs @@ -11,7 +11,7 @@ pub(crate) fn dir(bin: bool) -> anyhow::Result<()> { let bin = python_executable_dir()?; println!("{}", bin.simplified_display().cyan()); } else { - let installed_toolchains = ManagedPythonInstallations::from_settings() + let installed_toolchains = ManagedPythonInstallations::from_settings(None) .context("Failed to initialize toolchain settings")?; println!( "{}", diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 2fef5c0312b9..cd6011263897 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -121,6 +121,7 @@ impl Changelog { #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn install( project_dir: &Path, + install_dir: Option, targets: Vec, reinstall: bool, force: bool, @@ -178,7 +179,7 @@ pub(crate) async fn install( }; // Read the existing installations, lock the directory for the duration - let installations = ManagedPythonInstallations::from_settings()?.init()?; + let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?; let installations_dir = installations.root(); let scratch_dir = installations.scratch(); let _lock = installations.lock().await?; diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index e02dfc73bb4b..12556382ec74 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -22,12 +22,14 @@ use crate::printer::Printer; /// Uninstall managed Python versions. pub(crate) async fn uninstall( + install_dir: Option, targets: Vec, all: bool, printer: Printer, ) -> Result { - let installations = ManagedPythonInstallations::from_settings()?.init()?; + let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?; + let _lock = installations.lock().await?; // Perform the uninstallation. diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9e8db59e057f..5cfac3ef7fc0 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1104,6 +1104,7 @@ async fn run(mut cli: Cli) -> Result { commands::python_install( &project_dir, + args.install_dir, args.targets, args.reinstall, args.force, @@ -1127,7 +1128,7 @@ async fn run(mut 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, 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 05f1fb548172..58d9711dac80 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -760,6 +760,7 @@ impl PythonDirSettings { #[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, pub(crate) force: bool, @@ -784,6 +785,7 @@ impl PythonInstallSettings { let pypy_mirror = args.pypy_mirror.or(pypy_mirror); let PythonInstallArgs { + install_dir, targets, reinstall, force, @@ -793,6 +795,7 @@ impl PythonInstallSettings { } = args; Self { + install_dir, targets, reinstall, force, @@ -807,6 +810,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, } @@ -818,9 +822,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, + } } } diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index dc10d60e9749..76e4be305db7 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -1075,7 +1075,7 @@ pub fn venv_to_interpreter(venv: &Path) -> PathBuf { /// Get the path to the python interpreter for a specific python version. pub fn get_python(version: &PythonVersion) -> PathBuf { - ManagedPythonInstallations::from_settings() + ManagedPythonInstallations::from_settings(None) .map(|installed_pythons| { installed_pythons .find_version(version) diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index db67192ad3af..de2ed24b0356 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -450,7 +450,7 @@ fn help_subcommand() { fn help_subsubcommand() { let context = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.help().arg("python").arg("install"), @r##" + uv_snapshot!(context.filters(), context.help().arg("python").arg("install"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -483,6 +483,17 @@ fn help_subsubcommand() { See `uv help python` to view supported request formats. Options: + -i, --install-dir + The directory to store the Python installation in. + + If provided, `UV_PYTHON_INSTALL_DIR` will need to be set for subsequent operations for uv + to discover the Python installation. + + See `uv python dir` to view the current Python installation directory. Defaults to + `~/.local/share/uv/python`. + + [env: UV_PYTHON_INSTALL_DIR=] + --mirror Set the URL to use as the source for downloading Python installations. @@ -673,7 +684,7 @@ fn help_subsubcommand() { ----- stderr ----- - "##); + "###); } #[test] @@ -759,6 +770,8 @@ fn help_flag_subsubcommand() { [TARGETS]... The Python version(s) to install Options: + -i, --install-dir The directory to store the Python installation in [env: + UV_PYTHON_INSTALL_DIR=] --mirror Set the URL to use as the source for downloading Python installations [env: UV_PYTHON_INSTALL_MIRROR=] --pypy-mirror Set the URL to use as the source for downloading PyPy diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 5094a0b4209f..68bd0aebc472 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -74,7 +74,7 @@ fn python_install() { error: the following required arguments were not provided: ... - Usage: uv python uninstall ... + Usage: uv python uninstall --install-dir ... For more information, try '--help'. "###); @@ -209,7 +209,7 @@ fn python_install_preview() { error: the following required arguments were not provided: ... - Usage: uv python uninstall ... + Usage: uv python uninstall --install-dir ... For more information, try '--help'. "###); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 691abdef064c..c8eca0f4f078 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -4553,6 +4553,13 @@ uv python install [OPTIONS] [TARGETS]...
--help, -h

Display the concise help for this command

+
--install-dir, -i install-dir

The directory to store the Python installation in.

+ +

If provided, UV_PYTHON_INSTALL_DIR will need to be set for subsequent operations for uv to discover the Python installation.

+ +

See uv python dir to view the current Python installation directory. Defaults to ~/.local/share/uv/python.

+ +

May also be set with the UV_PYTHON_INSTALL_DIR environment variable.

--mirror mirror

Set the URL to use as the source for downloading Python installations.

The provided URL will replace https://github.com/indygreg/python-build-standalone/releases/download in, e.g., https://github.com/indygreg/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz.

@@ -5114,6 +5121,9 @@ uv python uninstall [OPTIONS] ...
--help, -h

Display the concise help for this command

+
--install-dir, -i install-dir

The directory where the Python was installed

+ +

May also be set with the UV_PYTHON_INSTALL_DIR environment variable.

--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).