mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Update uv python install --reinstall
to reinstall all previous versions (#11072)
Since we're shipping substantive updates to Python versions frequently, I want to lower the bar for reinstalling with the latest distributions. There's a follow-up task that's documented in a test case at https://github.com/astral-sh/uv/pull/11072/files#diff-f499c776e1d8cc5e55d7620786e32e8732b675abd98e246c0971130f5de9ed50R157-R158
This commit is contained in:
parent
d517b1ca26
commit
586bab32b9
3 changed files with 164 additions and 23 deletions
|
@ -28,6 +28,7 @@ use crate::implementation::{
|
|||
};
|
||||
use crate::installation::PythonInstallationKey;
|
||||
use crate::libc::LibcDetectionError;
|
||||
use crate::managed::ManagedPythonInstallation;
|
||||
use crate::platform::{self, Arch, Libc, Os};
|
||||
use crate::{Interpreter, PythonRequest, PythonVersion, VersionRequest};
|
||||
|
||||
|
@ -344,6 +345,23 @@ impl PythonDownloadRequest {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&ManagedPythonInstallation> for PythonDownloadRequest {
|
||||
fn from(installation: &ManagedPythonInstallation) -> Self {
|
||||
let key = installation.key();
|
||||
Self::new(
|
||||
Some(VersionRequest::from(&key.version())),
|
||||
match &key.implementation {
|
||||
LenientImplementationName::Known(implementation) => Some(*implementation),
|
||||
LenientImplementationName::Unknown(name) => unreachable!("Managed Python installations are expected to always have known implementation names, found {name}"),
|
||||
},
|
||||
Some(key.arch),
|
||||
Some(key.os),
|
||||
Some(key.libc),
|
||||
Some(key.prerelease.is_some()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PythonDownloadRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut parts = Vec::new();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -164,7 +165,12 @@ pub(crate) async fn install(
|
|||
.unwrap_or_else(|| {
|
||||
// If no version file is found and no requests were made
|
||||
is_default_install = true;
|
||||
vec![PythonRequest::Default]
|
||||
vec![if reinstall {
|
||||
// On bare `--reinstall`, reinstall all Python versions
|
||||
PythonRequest::Any
|
||||
} else {
|
||||
PythonRequest::Default
|
||||
}]
|
||||
})
|
||||
.into_iter()
|
||||
.map(InstallRequest::new)
|
||||
|
@ -193,35 +199,76 @@ pub(crate) async fn install(
|
|||
|
||||
// Find requests that are already satisfied
|
||||
let mut changelog = Changelog::default();
|
||||
let (satisfied, unsatisfied): (Vec<_>, Vec<_>) = requests.iter().partition_map(|request| {
|
||||
if let Some(installation) = existing_installations
|
||||
.iter()
|
||||
.find(|installation| request.matches_installation(installation))
|
||||
{
|
||||
changelog.existing.insert(installation.key().clone());
|
||||
if reinstall {
|
||||
debug!(
|
||||
"Ignoring match `{}` for request `{}` due to `--reinstall` flag",
|
||||
installation.key().green(),
|
||||
request.cyan()
|
||||
);
|
||||
let (satisfied, unsatisfied): (Vec<_>, Vec<_>) = if reinstall {
|
||||
// In the reinstall case, we want to iterate over all matching installations instead of
|
||||
// stopping at the first match.
|
||||
|
||||
Either::Right(request)
|
||||
} else {
|
||||
let mut unsatisfied: Vec<Cow<InstallRequest>> =
|
||||
Vec::with_capacity(existing_installations.len() + requests.len());
|
||||
|
||||
for request in &requests {
|
||||
if existing_installations.is_empty() {
|
||||
debug!("No installation found for request `{}`", request.cyan());
|
||||
unsatisfied.push(Cow::Borrowed(request));
|
||||
}
|
||||
|
||||
for installation in existing_installations
|
||||
.iter()
|
||||
.filter(|installation| request.matches_installation(installation))
|
||||
{
|
||||
changelog.existing.insert(installation.key().clone());
|
||||
if matches!(&request.request, &PythonRequest::Any) {
|
||||
// Construct a install request matching the existing installation
|
||||
match InstallRequest::new(PythonRequest::Key(installation.into())) {
|
||||
Ok(request) => {
|
||||
debug!("Will reinstall `{}`", installation.key().green());
|
||||
unsatisfied.push(Cow::Owned(request));
|
||||
}
|
||||
Err(err) => {
|
||||
// This shouldn't really happen, but maybe a new version of uv dropped
|
||||
// support for a key we previously supported
|
||||
warn_user!(
|
||||
"Failed to create reinstall request for existing installation `{}`: {err}",
|
||||
installation.key().green()
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO(zanieb): This isn't really right! But we need `--upgrade` or similar
|
||||
// to handle this case correctly without causing a breaking change.
|
||||
|
||||
// If we have real requests, just ignore the existing installation
|
||||
debug!(
|
||||
"Ignoring match `{}` for request `{}` due to `--reinstall` flag",
|
||||
installation.key().green(),
|
||||
request.cyan()
|
||||
);
|
||||
unsatisfied.push(Cow::Borrowed(request));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(vec![], unsatisfied)
|
||||
} else {
|
||||
// If we can find one existing installation that matches the request, it is satisfied
|
||||
requests.iter().partition_map(|request| {
|
||||
if let Some(installation) = existing_installations
|
||||
.iter()
|
||||
.find(|installation| request.matches_installation(installation))
|
||||
{
|
||||
debug!(
|
||||
"Found `{}` for request `{}`",
|
||||
installation.key().green(),
|
||||
request.cyan(),
|
||||
);
|
||||
|
||||
Either::Left(installation)
|
||||
} else {
|
||||
debug!("No installation found for request `{}`", request.cyan());
|
||||
Either::Right(Cow::Borrowed(request))
|
||||
}
|
||||
} else {
|
||||
debug!("No installation found for request `{}`", request.cyan());
|
||||
|
||||
Either::Right(request)
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
// Check if Python downloads are banned
|
||||
if matches!(python_downloads, PythonDownloads::Never) && !unsatisfied.is_empty() {
|
||||
|
|
|
@ -54,7 +54,7 @@ fn python_install() {
|
|||
"###);
|
||||
|
||||
// You can opt-in to a reinstall
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--reinstall"), @r###"
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.13").arg("--reinstall"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -91,6 +91,82 @@ fn python_install() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_reinstall() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
|
||||
// Install a couple versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("3.13"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed 2 versions in [TIME]
|
||||
+ cpython-3.12.8-[PLATFORM]
|
||||
+ cpython-3.13.1-[PLATFORM]
|
||||
"###);
|
||||
|
||||
// Reinstall a single version
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.13").arg("--reinstall"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.1 in [TIME]
|
||||
~ cpython-3.13.1-[PLATFORM]
|
||||
"###);
|
||||
|
||||
// Reinstall multiple versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--reinstall"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed 2 versions in [TIME]
|
||||
~ cpython-3.12.8-[PLATFORM]
|
||||
~ cpython-3.13.1-[PLATFORM]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_reinstall_patch() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
|
||||
// Install a couple patch versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.12.6").arg("3.12.7"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed 2 versions in [TIME]
|
||||
+ cpython-3.12.6-[PLATFORM]
|
||||
+ cpython-3.12.7-[PLATFORM]
|
||||
"###);
|
||||
|
||||
// Reinstall all "3.12" versions
|
||||
// TODO(zanieb): This doesn't work today, because we need this to install the "latest" as there
|
||||
// is no workflow for `--upgrade` yet
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("--reinstall"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.12.8 in [TIME]
|
||||
+ cpython-3.12.8-[PLATFORM]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_install_automatic() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue