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.
This commit is contained in:
Oshadha Gunawardena 2025-11-13 00:03:58 +05:30 committed by GitHub
parent 4fac4cb7ed
commit aec42540a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 8 deletions

View File

@ -264,7 +264,7 @@ pub(crate) async fn install(
.collect::<Result<Vec<_>>>()? .collect::<Result<Vec<_>>>()?
}; };
let Some(first_request) = requests.first() else { if requests.is_empty() {
if upgrade { if upgrade {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
@ -272,7 +272,7 @@ pub(crate) async fn install(
)?; )?;
} }
return Ok(ExitStatus::Success); return Ok(ExitStatus::Success);
}; }
let requested_minor_versions = requests let requested_minor_versions = requests
.iter() .iter()
@ -500,7 +500,6 @@ pub(crate) async fn install(
upgradeable, upgradeable,
upgrade, upgrade,
is_default_install, is_default_install,
first_request,
&existing_installations, &existing_installations,
&installations, &installations,
&mut changelog, &mut changelog,
@ -764,7 +763,6 @@ fn create_bin_links(
upgradeable: bool, upgradeable: bool,
upgrade: bool, upgrade: bool,
is_default_install: bool, is_default_install: bool,
first_request: &InstallRequest,
existing_installations: &[ManagedPythonInstallation], existing_installations: &[ManagedPythonInstallation],
installations: &[&ManagedPythonInstallation], installations: &[&ManagedPythonInstallation],
changelog: &mut Changelog, 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 // 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 // it. In particular, it may be confusing because it does not apply when versions are loaded
// from a `.python-version` file. // from a `.python-version` file.
let targets = if (default let should_create_default_links = default
|| (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT))) || (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT));
&& first_request.matches_installation(installation)
{ let targets = if should_create_default_links {
vec![ vec![
installation.key().executable_name_minor(), installation.key().executable_name_minor(),
installation.key().executable_name_major(), installation.key().executable_name_major(),

View File

@ -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] #[test]
fn python_install_default_from_env() { fn python_install_default_from_env() {
let context: TestContext = TestContext::new_with_versions(&[]) let context: TestContext = TestContext::new_with_versions(&[])