From 4ce3853c52bfc82e8bcff62596c1e7d1c9b9953a Mon Sep 17 00:00:00 2001 From: cui Date: Fri, 13 Feb 2026 01:46:40 +0800 Subject: [PATCH] Do not match cpython 3.10 for cpython-3.1 (#17972) ## Summary bug fix, in find version 3.1 should not match 3.10 ## Test Plan --------- Co-authored-by: konstin Co-authored-by: Zanie Blue --- crates/uv-python/src/discovery.rs | 10 +++++++- crates/uv-python/src/managed.rs | 39 +++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index c36317162..67a3c2bac 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -27,7 +27,7 @@ use which::{which, which_all}; use crate::downloads::{ManagedPythonDownloadList, PlatformRequest, PythonDownloadRequest}; use crate::implementation::ImplementationName; -use crate::installation::PythonInstallation; +use crate::installation::{PythonInstallation, PythonInstallationKey}; use crate::interpreter::Error as InterpreterError; use crate::interpreter::{StatusCodeError, UnexpectedResponseError}; use crate::managed::{ManagedPythonInstallations, PythonMinorVersionLink}; @@ -2975,6 +2975,14 @@ impl VersionRequest { } } + /// Check if a [`PythonInstallationKey`] is compatible with the request. + /// + /// WARNING: Use [`VersionRequest::matches_interpreter`] too. This method is only suitable to + /// avoid querying interpreters if it's clear it cannot fulfill the request. + pub(crate) fn matches_installation_key(&self, key: &PythonInstallationKey) -> bool { + self.matches_major_minor_patch_prerelease(key.major, key.minor, key.patch, key.prerelease()) + } + /// Whether a patch version segment is present in the request. fn has_patch(&self) -> bool { match self { diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 307eede29..ee6fc265a 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -24,6 +24,7 @@ use uv_state::{StateBucket, StateStore}; use uv_static::EnvVars; use uv_trampoline_builder::{Launcher, LauncherKind}; +use crate::discovery::VersionRequest; use crate::downloads::{Error as DownloadError, ManagedPythonDownload}; use crate::implementation::{ Error as ImplementationError, ImplementationName, LenientImplementationName, @@ -305,15 +306,10 @@ impl ManagedPythonInstallations { &'a self, version: &'a PythonVersion, ) -> Result + 'a, Error> { + let request = VersionRequest::from(version); Ok(self .find_matching_current_platform()? - .filter(move |installation| { - installation - .path - .file_name() - .map(OsStr::to_string_lossy) - .is_some_and(|filename| filename.starts_with(&format!("cpython-{version}"))) - })) + .filter(move |installation| request.matches_installation_key(installation.key()))) } pub fn root(&self) -> &Path { @@ -1399,4 +1395,33 @@ mod tests { // Older patch version should not be an upgrade even with newer build assert!(!older_patch_newer_build.is_upgrade_of(&newer_patch_older_build)); } + + #[test] + fn test_find_version_matching() { + use crate::PythonVersion; + + let platform = Platform::from_env().unwrap(); + let temp_dir = tempfile::tempdir().unwrap(); + + // Create mock installation directories + fs::create_dir(temp_dir.path().join(format!("cpython-3.10.0-{platform}"))).unwrap(); + + temp_env::with_var( + uv_static::EnvVars::UV_PYTHON_INSTALL_DIR, + Some(temp_dir.path()), + || { + let installations = ManagedPythonInstallations::from_settings(None).unwrap(); + + // Version 3.1 should NOT match 3.10 + let v3_1 = PythonVersion::from_str("3.1").unwrap(); + let matched: Vec<_> = installations.find_version(&v3_1).unwrap().collect(); + assert_eq!(matched.len(), 0); + + // Check that 3.10 matches + let v3_10 = PythonVersion::from_str("3.10").unwrap(); + let matched: Vec<_> = installations.find_version(&v3_10).unwrap().collect(); + assert_eq!(matched.len(), 1); + }, + ); + } }