diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 1975a5a49dd..3cadf420c09 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -455,6 +455,11 @@ impl PythonDownloadRequest { self.implementation.as_ref() } + /// Return the requested build, if any. + pub fn build(&self) -> Option<&str> { + self.build.as_deref() + } + pub(crate) fn version(&self) -> Option<&VersionRequest> { self.version.as_ref() } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 4c4704f9f70..3f6bad393a0 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -554,10 +554,19 @@ async fn perform_install( } } else if let Some(installation) = existing_installations .iter() - .find(|inst| request.matches_installation(inst)) + .find(|inst| request.download_request.satisfied_by_key(inst.key())) { - debug!("Found `{}` for request `{}`", installation.key(), request); - satisfied.push(installation); + if matches_build(request.download_request.build(), installation.build()) { + debug!("Found `{}` for request `{}`", installation.key(), request); + satisfied.push(installation); + } else { + debug!( + "Build version mismatch for `{}`, will reinstall", + installation.key() + ); + changelog.existing.insert(installation.key().clone()); + unsatisfied.push(Cow::Borrowed(request)); + } } else { debug!("No installation found for request `{}`", request); unsatisfied.push(Cow::Borrowed(request)); @@ -609,6 +618,10 @@ async fn perform_install( let mut tasks = futures::stream::iter(&downloads) .map(async |download| { + let reinstall = reinstall + || existing_installations + .iter() + .any(|installation| download.key() == installation.key()); ( *download, download diff --git a/crates/uv/tests/python/python_install.rs b/crates/uv/tests/python/python_install.rs index b0e502738a1..c268579d000 100644 --- a/crates/uv/tests/python/python_install.rs +++ b/crates/uv/tests/python/python_install.rs @@ -4110,6 +4110,29 @@ fn python_install_build_version() { let build_content = fs_err::read_to_string(&build_file_path).unwrap(); assert_eq!(build_content, "20240814"); + // A matching patch with an outdated build must be re-extracted for an explicit build pin. + let stale_file = cpython_dir.child("stale-file"); + stale_file.touch().unwrap(); + fs_err::write(&build_file_path, "19000101").unwrap(); + + uv_snapshot!(context.filters(), context.python_install() + .arg("3.12.5") + .env(EnvVars::UV_PYTHON_CPYTHON_BUILD, "20240814"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.12.5 in [TIME] + ~ cpython-3.12.5-[PLATFORM] + "); + + assert!(!stale_file.exists()); + assert_eq!( + fs_err::read_to_string(&build_file_path).unwrap(), + "20240814" + ); + // We should find the build uv_snapshot!(context.filters(), context.python_find() .arg("3.12")