From aec42540a1e99527bb56266eb60b03a3094f9933 Mon Sep 17 00:00:00 2001 From: Oshadha Gunawardena Date: Thu, 13 Nov 2025 00:03:58 +0530 Subject: [PATCH] Fix handling of `python install --default` for pre-release Python versions (#16706) ## Summary Fixes `--default` not creating default executable links for pre-release Python versions. When using `--default` with a pre-release version like `3.15.0a1`, the code was checking `matches_installation()` against the download request instead of the original user request. This caused the check to fail since the download request doesn't match pre-release versions the same way. Changed it to use `installation.satisfies(&first_request.request)` when `--default` is used, which checks against the original user request. Fixes #16696 ## Test Plan Added `python_install_default_prerelease` test that installs Python 3.15 with `--default` and verifies all three executable links (`python3.15`, `python3`, `python`) are created. The test skips gracefully if 3.15 isn't available. All existing tests pass. --- crates/uv/src/commands/python/install.rs | 14 ++++----- crates/uv/tests/it/python_install.rs | 39 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 52561b70a..6486b2cf7 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -264,7 +264,7 @@ pub(crate) async fn install( .collect::>>()? }; - let Some(first_request) = requests.first() else { + if requests.is_empty() { if upgrade { writeln!( printer.stderr(), @@ -272,7 +272,7 @@ pub(crate) async fn install( )?; } return Ok(ExitStatus::Success); - }; + } let requested_minor_versions = requests .iter() @@ -500,7 +500,6 @@ pub(crate) async fn install( upgradeable, upgrade, is_default_install, - first_request, &existing_installations, &installations, &mut changelog, @@ -764,7 +763,6 @@ fn create_bin_links( upgradeable: bool, upgrade: bool, is_default_install: bool, - first_request: &InstallRequest, existing_installations: &[ManagedPythonInstallation], installations: &[&ManagedPythonInstallation], changelog: &mut Changelog, @@ -774,10 +772,10 @@ fn create_bin_links( // TODO(zanieb): We want more feedback on the `is_default_install` behavior before stabilizing // it. In particular, it may be confusing because it does not apply when versions are loaded // from a `.python-version` file. - let targets = if (default - || (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT))) - && first_request.matches_installation(installation) - { + let should_create_default_links = default + || (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT)); + + let targets = if should_create_default_links { vec![ installation.key().executable_name_minor(), installation.key().executable_name_major(), diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 5cd76d8e8..86da14eb4 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -2255,6 +2255,45 @@ fn python_install_broken_link() { }); } +/// Test that --default works with pre-release versions (e.g., 3.15.0a1). +/// This test verifies the fix for issue #16696 where --default didn't create +/// python.exe and python3.exe links for pre-release versions. +#[test] +fn python_install_default_prerelease() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs() + .with_python_download_cache(); + + // Install Python 3.15, which currently only exists as a pre-release (3.15.0a1). + context + .python_install() + .arg("--default") + .arg("--preview-features") + .arg("python-install-default") + .arg("3.15") + .assert() + .success(); + + let bin_python_minor_15 = context + .bin_dir + .child(format!("python3.15{}", std::env::consts::EXE_SUFFIX)); + + let bin_python_major = context + .bin_dir + .child(format!("python3{}", std::env::consts::EXE_SUFFIX)); + + let bin_python_default = context + .bin_dir + .child(format!("python{}", std::env::consts::EXE_SUFFIX)); + + // Verify that all three executables are created when --default is used with a pre-release version + bin_python_minor_15.assert(predicate::path::exists()); + bin_python_major.assert(predicate::path::exists()); + bin_python_default.assert(predicate::path::exists()); +} + #[test] fn python_install_default_from_env() { let context: TestContext = TestContext::new_with_versions(&[])