Add an ArchMode to filter displayed architectures in uv python list

This commit is contained in:
Zanie Blue 2025-05-28 09:04:08 -05:00
parent e5d002beb1
commit 7ca43dd366
5 changed files with 89 additions and 11 deletions

View file

@ -2799,7 +2799,8 @@ mod tests {
arch: None,
os: None,
libc: None,
prereleases: None
prereleases: None,
arch_mode: None,
})
);
assert_eq!(
@ -2818,7 +2819,8 @@ mod tests {
}),
os: Some(Os(target_lexicon::OperatingSystem::Darwin(None))),
libc: Some(Libc::None),
prereleases: None
prereleases: None,
arch_mode: None,
})
);
assert_eq!(
@ -2834,7 +2836,8 @@ mod tests {
arch: None,
os: None,
libc: None,
prereleases: None
prereleases: None,
arch_mode: None,
})
);
assert_eq!(
@ -2853,7 +2856,8 @@ mod tests {
}),
os: None,
libc: None,
prereleases: None
prereleases: None,
arch_mode: None,
})
);

View file

@ -36,7 +36,7 @@ use crate::implementation::{
use crate::installation::PythonInstallationKey;
use crate::libc::LibcDetectionError;
use crate::managed::ManagedPythonInstallation;
use crate::platform::{self, Arch, Libc, Os};
use crate::platform::{self, Arch, ArchMode, Libc, Os};
use crate::{Interpreter, PythonRequest, PythonVersion, VersionRequest};
#[derive(Error, Debug)]
@ -123,6 +123,8 @@ pub struct PythonDownloadRequest {
/// Whether to allow pre-releases or not. If not set, defaults to true if [`Self::version`] is
/// not None, and false otherwise.
pub(crate) prereleases: Option<bool>,
pub(crate) arch_mode: Option<ArchMode>,
}
impl PythonDownloadRequest {
@ -133,6 +135,7 @@ impl PythonDownloadRequest {
os: Option<Os>,
libc: Option<Libc>,
prereleases: Option<bool>,
arch_mode: Option<ArchMode>,
) -> Self {
Self {
version,
@ -141,6 +144,7 @@ impl PythonDownloadRequest {
os,
libc,
prereleases,
arch_mode,
}
}
@ -186,6 +190,12 @@ impl PythonDownloadRequest {
self
}
#[must_use]
pub fn with_arch_mode(mut self, arch_mode: ArchMode) -> Self {
self.arch_mode = Some(arch_mode);
self
}
/// Construct a new [`PythonDownloadRequest`] from a [`PythonRequest`] if possible.
///
/// Returns [`None`] if the request kind is not compatible with a download, e.g., it is
@ -247,6 +257,7 @@ impl PythonDownloadRequest {
Some(Os::from_env()),
Some(Libc::from_env()?),
None,
None,
))
}
@ -291,6 +302,11 @@ impl PythonDownloadRequest {
if !arch.supports(key.arch) {
return false;
}
if let Some(mode) = self.arch_mode {
if !mode.allows(arch, &key.arch) {
return false;
}
}
}
if let Some(libc) = &self.libc {
@ -412,6 +428,7 @@ impl From<&ManagedPythonInstallation> for PythonDownloadRequest {
Some(key.os),
Some(key.libc),
Some(key.prerelease.is_some()),
None,
)
}
}
@ -483,7 +500,15 @@ impl FromStr for PythonDownloadRequest {
_ => return Err(Error::TooManyParts(s.to_string())),
}
}
Ok(Self::new(version, implementation, arch, os, libc, None))
Ok(Self::new(
version,
implementation,
arch,
os,
libc,
None,
None,
))
}
}

View file

@ -31,6 +31,17 @@ pub enum ArchVariant {
V4,
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Default)]
pub enum ArchMode {
/// Select the most precise architecture matching the current platform, e.g., x86-64-v4
#[default]
BestNative,
/// Select the most compatible architecture matching the current platform, e.g., x86-64-v1
CompatibleNative,
/// Select an emulated architecture, e.g., x86-64 on aarch64 macOS.
Emulated,
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub struct Arch {
pub(crate) family: target_lexicon::Architecture,
@ -95,6 +106,37 @@ impl Os {
}
}
impl ArchMode {
pub fn allows(self, current: &Arch, other: &Arch) -> bool {
match self {
Self::CompatibleNative | Self::BestNative => {
// The architecture is native if the family is equal
if current.family != other.family {
return false;
}
// There is only a compatibility nuance for x86_64 here
if current.family != target_lexicon::Architecture::X86_64 {
return true;
}
if matches!(self, Self::CompatibleNative) {
// Only allow x86_64 without a variant
return other.variant.is_none();
} else if matches!(self, Self::BestNative) {
// Only allow the variant matching the current architecture
return current.variant == other.variant;
}
// We should handle all cases above
unreachable!();
}
// The architecture is emulated if the family differs
Self::Emulated => current.family != other.family,
}
}
}
impl Arch {
pub fn from_env() -> Self {
Self {

View file

@ -17,7 +17,7 @@ use uv_python::downloads::{self, DownloadResult, ManagedPythonDownload, PythonDo
use uv_python::managed::{
ManagedPythonInstallation, ManagedPythonInstallations, python_executable_dir,
};
use uv_python::platform::{Arch, Libc};
use uv_python::platform::{Arch, ArchMode, Libc};
use uv_python::{
PythonDownloads, PythonInstallationKey, PythonRequest, PythonVersionFile,
VersionFileDiscoveryOptions, VersionFilePreference,
@ -51,8 +51,7 @@ impl InstallRequest {
"`{}` is not a valid Python download request; see `uv help python` for supported formats and `uv python list --only-downloads` for available versions",
request.to_canonical_string()
)
})?
.fill()?;
})?.with_arch_mode(ArchMode::BestNative).fill()?;
// Find a matching download
let download =

View file

@ -3,6 +3,7 @@ use std::collections::BTreeSet;
use std::fmt::Write;
use uv_cli::PythonListFormat;
use uv_pep440::Version;
use uv_python::platform::ArchMode;
use anyhow::Result;
use itertools::Either;
@ -80,7 +81,10 @@ pub(crate) async fn list(
PythonListKinds::Downloads => Some(if all_platforms {
base_download_request
} else {
base_download_request.fill_platform()?
base_download_request
.fill_platform()?
// Only show the best, native architecture by default
.with_arch_mode(ArchMode::BestNative)
}),
PythonListKinds::Default => {
if python_downloads.is_automatic() {
@ -89,7 +93,11 @@ pub(crate) async fn list(
} else if all_arches {
base_download_request.fill_platform()?.with_any_arch()
} else {
base_download_request.fill_platform()?
base_download_request
.fill_platform()?
// Only show the best, native architecture by default
// TODO(zanieb): We should expose this option to the user
.with_arch_mode(ArchMode::BestNative)
})
} else {
// If fetching is not automatic, then don't show downloads as available by default