mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-18 19:21:46 +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())
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// trimmed.
|
||||
#[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>`
|
||||
pub fn equals_version(version: Version) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ use crate::virtualenv::{
|
|||
};
|
||||
#[cfg(windows)]
|
||||
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.
|
||||
///
|
||||
|
|
@ -2457,9 +2457,26 @@ impl fmt::Display for ExecutableName {
|
|||
}
|
||||
|
||||
impl VersionRequest {
|
||||
/// Derive a [`VersionRequest::MajorMinor`] from a [`PythonInstallationKey`]
|
||||
pub fn major_minor_request_from_key(key: &PythonInstallationKey) -> Self {
|
||||
Self::MajorMinor(key.major, key.minor, key.variant)
|
||||
/// Drop any patch or prerelease information from the version request.
|
||||
#[must_use]
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -411,6 +411,10 @@ impl PythonDownloadRequest {
|
|||
self.libc.as_ref()
|
||||
}
|
||||
|
||||
pub fn take_version(&mut self) -> Option<VersionRequest> {
|
||||
self.version.take()
|
||||
}
|
||||
|
||||
/// Iterate over all [`PythonDownload`]'s that match this request.
|
||||
pub fn iter_downloads<'a>(
|
||||
&'a self,
|
||||
|
|
|
|||
|
|
@ -210,15 +210,19 @@ pub(crate) async fn install(
|
|||
let requests: Vec<_> = if targets.is_empty() {
|
||||
if upgrade {
|
||||
is_unspecified_upgrade = true;
|
||||
// On upgrade, derive requests for all of the existing installations
|
||||
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
||||
for installation in &existing_installations {
|
||||
let request = VersionRequest::major_minor_request_from_key(installation.key());
|
||||
if let Ok(request) = InstallRequest::new(
|
||||
PythonRequest::Version(request),
|
||||
let mut request = PythonDownloadRequest::from(installation);
|
||||
// We should always have a version in the request from an existing installation
|
||||
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(),
|
||||
) {
|
||||
minor_version_requests.insert(request);
|
||||
}
|
||||
)?;
|
||||
minor_version_requests.insert(install_request);
|
||||
}
|
||||
minor_version_requests.into_iter().collect::<Vec<_>>()
|
||||
} else {
|
||||
|
|
@ -284,13 +288,14 @@ pub(crate) async fn install(
|
|||
.collect::<IndexSet<_>>();
|
||||
|
||||
if upgrade
|
||||
&& requests.iter().any(|request| {
|
||||
&& let Some(request) = requests.iter().find(|request| {
|
||||
request.request.includes_patch() || request.request.includes_prerelease()
|
||||
})
|
||||
{
|
||||
writeln!(
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1230,7 +1230,7 @@ fn python_upgrade_not_allowed() {
|
|||
----- stdout -----
|
||||
|
||||
----- 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
|
||||
|
|
@ -1240,7 +1240,7 @@ fn python_upgrade_not_allowed() {
|
|||
----- stdout -----
|
||||
|
||||
----- 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 anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::fixture::FileTouch;
|
||||
use assert_fs::prelude::PathChild;
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ fn python_upgrade() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `uv python upgrade` only accepts minor versions
|
||||
error: `uv python upgrade` only accepts minor versions, got: 3.10.17
|
||||
");
|
||||
|
||||
// Upgrade patch version
|
||||
|
|
@ -737,3 +738,27 @@ fn python_upgrade_force_install() -> Result<()> {
|
|||
|
||||
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