mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-19 11:35:36 +00:00
Check for matching Python implementation during uv python upgrade (#16420)
Closes https://github.com/astral-sh/uv/issues/16416
This commit is contained in:
parent
00bf80bfda
commit
1fbc1c7ff4
7 changed files with 82 additions and 15 deletions
|
|
@ -615,6 +615,13 @@ impl Version {
|
||||||
Self::new(self.release().iter().copied())
|
Self::new(self.release().iter().copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the version with any segments apart from the minor version of the release removed.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn only_minor_release(&self) -> Self {
|
||||||
|
Self::new(self.release().iter().take(2).copied())
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the version with any segments apart from the release removed, with trailing zeroes
|
/// Return the version with any segments apart from the release removed, with trailing zeroes
|
||||||
/// trimmed.
|
/// trimmed.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,15 @@ impl VersionSpecifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all parts of the version beyond the minor segment of the release.
|
||||||
|
#[must_use]
|
||||||
|
pub fn only_minor_release(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
operator: self.operator,
|
||||||
|
version: self.version.only_minor_release(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `==<version>`
|
/// `==<version>`
|
||||||
pub fn equals_version(version: Version) -> Self {
|
pub fn equals_version(version: Version) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ use crate::virtualenv::{
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::windows_registry::{WindowsPython, registry_pythons};
|
use crate::windows_registry::{WindowsPython, registry_pythons};
|
||||||
use crate::{BrokenSymlink, Interpreter, PythonInstallationKey, PythonVersion};
|
use crate::{BrokenSymlink, Interpreter, PythonVersion};
|
||||||
|
|
||||||
/// A request to find a Python installation.
|
/// A request to find a Python installation.
|
||||||
///
|
///
|
||||||
|
|
@ -2457,9 +2457,26 @@ impl fmt::Display for ExecutableName {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionRequest {
|
impl VersionRequest {
|
||||||
/// Derive a [`VersionRequest::MajorMinor`] from a [`PythonInstallationKey`]
|
/// Drop any patch or prerelease information from the version request.
|
||||||
pub fn major_minor_request_from_key(key: &PythonInstallationKey) -> Self {
|
#[must_use]
|
||||||
Self::MajorMinor(key.major, key.minor, key.variant)
|
pub fn only_minor(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Any => self,
|
||||||
|
Self::Default => self,
|
||||||
|
Self::Range(specifiers, variant) => Self::Range(
|
||||||
|
specifiers
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.only_minor_release())
|
||||||
|
.collect(),
|
||||||
|
variant,
|
||||||
|
),
|
||||||
|
Self::Major(..) => self,
|
||||||
|
Self::MajorMinor(..) => self,
|
||||||
|
Self::MajorMinorPatch(major, minor, _, variant)
|
||||||
|
| Self::MajorMinorPrerelease(major, minor, _, variant) => {
|
||||||
|
Self::MajorMinor(major, minor, variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return possible executable names for the given version request.
|
/// Return possible executable names for the given version request.
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,10 @@ impl PythonDownloadRequest {
|
||||||
self.libc.as_ref()
|
self.libc.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_version(&mut self) -> Option<VersionRequest> {
|
||||||
|
self.version.take()
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all [`PythonDownload`]'s that match this request.
|
/// Iterate over all [`PythonDownload`]'s that match this request.
|
||||||
pub fn iter_downloads<'a>(
|
pub fn iter_downloads<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
|
||||||
|
|
@ -210,15 +210,19 @@ pub(crate) async fn install(
|
||||||
let requests: Vec<_> = if targets.is_empty() {
|
let requests: Vec<_> = if targets.is_empty() {
|
||||||
if upgrade {
|
if upgrade {
|
||||||
is_unspecified_upgrade = true;
|
is_unspecified_upgrade = true;
|
||||||
|
// On upgrade, derive requests for all of the existing installations
|
||||||
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
||||||
for installation in &existing_installations {
|
for installation in &existing_installations {
|
||||||
let request = VersionRequest::major_minor_request_from_key(installation.key());
|
let mut request = PythonDownloadRequest::from(installation);
|
||||||
if let Ok(request) = InstallRequest::new(
|
// We should always have a version in the request from an existing installation
|
||||||
PythonRequest::Version(request),
|
let version = request.take_version().unwrap();
|
||||||
|
// Drop the patch and prerelease parts from the request
|
||||||
|
request = request.with_version(version.only_minor());
|
||||||
|
let install_request = InstallRequest::new(
|
||||||
|
PythonRequest::Key(request),
|
||||||
python_downloads_json_url.as_deref(),
|
python_downloads_json_url.as_deref(),
|
||||||
) {
|
)?;
|
||||||
minor_version_requests.insert(request);
|
minor_version_requests.insert(install_request);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
minor_version_requests.into_iter().collect::<Vec<_>>()
|
minor_version_requests.into_iter().collect::<Vec<_>>()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -284,13 +288,14 @@ pub(crate) async fn install(
|
||||||
.collect::<IndexSet<_>>();
|
.collect::<IndexSet<_>>();
|
||||||
|
|
||||||
if upgrade
|
if upgrade
|
||||||
&& requests.iter().any(|request| {
|
&& let Some(request) = requests.iter().find(|request| {
|
||||||
request.request.includes_patch() || request.request.includes_prerelease()
|
request.request.includes_patch() || request.request.includes_prerelease()
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"error: `uv python upgrade` only accepts minor versions"
|
"error: `uv python upgrade` only accepts minor versions, got: {}",
|
||||||
|
request.request.to_canonical_string()
|
||||||
)?;
|
)?;
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1230,7 +1230,7 @@ fn python_upgrade_not_allowed() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: `uv python upgrade` only accepts minor versions
|
error: `uv python upgrade` only accepts minor versions, got: 3.13.0
|
||||||
");
|
");
|
||||||
|
|
||||||
// Request a pre-release upgrade
|
// Request a pre-release upgrade
|
||||||
|
|
@ -1240,7 +1240,7 @@ fn python_upgrade_not_allowed() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: `uv python upgrade` only accepts minor versions
|
error: `uv python upgrade` only accepts minor versions, got: 3.14rc3
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::common::{TestContext, uv_snapshot};
|
use crate::common::{TestContext, uv_snapshot};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use assert_cmd::assert::OutputAssertExt;
|
||||||
use assert_fs::fixture::FileTouch;
|
use assert_fs::fixture::FileTouch;
|
||||||
use assert_fs::prelude::PathChild;
|
use assert_fs::prelude::PathChild;
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ fn python_upgrade() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: `uv python upgrade` only accepts minor versions
|
error: `uv python upgrade` only accepts minor versions, got: 3.10.17
|
||||||
");
|
");
|
||||||
|
|
||||||
// Upgrade patch version
|
// Upgrade patch version
|
||||||
|
|
@ -737,3 +738,27 @@ fn python_upgrade_force_install() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_upgrade_implementation() {
|
||||||
|
let context = TestContext::new_with_versions(&[])
|
||||||
|
.with_python_download_cache()
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_empty_python_install_mirror()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
// Install pypy
|
||||||
|
context.python_install().arg("pypy@3.11").assert().success();
|
||||||
|
|
||||||
|
// Run the upgrade, we should not install cpython
|
||||||
|
uv_snapshot!(context.filters(), context.python_upgrade(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv python upgrade` is experimental and may change without warning. Pass `--preview-features python-upgrade` to disable this warning
|
||||||
|
All versions already on latest supported patch release
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue