diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 870171cdc..a0baa96c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2090,6 +2090,28 @@ jobs: - name: "Validate global Python install" run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe + system-test-windows-aarch64-aarch64-python-313: + timeout-minutes: 10 + needs: build-binary-windows-aarch64 + name: "check system | aarch64 python3.13 on windows aarch64" + runs-on: github-windows-11-aarch64-4 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + architecture: "arm64" + allow-prereleases: true + + - name: "Download binary" + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: uv-windows-aarch64-${{ github.sha }} + + - name: "Validate global Python install" + run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe + # Test our PEP 514 integration that installs Python into the Windows registry. system-test-windows-registry: timeout-minutes: 10 diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index 025592c37..ce8620ae2 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -43,15 +43,36 @@ impl Ord for Arch { return self.variant.cmp(&other.variant); } - let native = Arch::from_env(); + // For the time being, manually make aarch64 windows disfavored + // on its own host platform, because most packages don't have wheels for + // aarch64 windows, making emulation more useful than native execution! + // + // The reason we do this in "sorting" and not "supports" is so that we don't + // *refuse* to use an aarch64 windows pythons if they happen to be installed + // and nothing else is available. + // + // Similarly if someone manually requests an aarch64 windows install, we + // should respect that request (this is the way users should "override" + // this behaviour). + let preferred = if cfg!(all(windows, target_arch = "aarch64")) { + Arch { + family: target_lexicon::Architecture::X86_64, + variant: None, + } + } else { + // Prefer native architectures + Arch::from_env() + }; - // Prefer native architectures - match (self.family == native.family, other.family == native.family) { + match ( + self.family == preferred.family, + other.family == preferred.family, + ) { (true, true) => unreachable!(), (true, false) => std::cmp::Ordering::Less, (false, true) => std::cmp::Ordering::Greater, (false, false) => { - // Both non-native, fallback to lexicographic order + // Both non-preferred, fallback to lexicographic order self.family.to_string().cmp(&other.family.to_string()) } } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 08f95003e..3df0cf91d 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::BTreeMap; use std::fmt::Write; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -15,7 +16,9 @@ use tracing::{debug, trace}; use uv_configuration::PreviewMode; use uv_fs::Simplified; -use uv_python::downloads::{self, DownloadResult, ManagedPythonDownload, PythonDownloadRequest}; +use uv_python::downloads::{ + self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest, +}; use uv_python::managed::{ ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink, create_link_to_executable, python_executable_dir, @@ -401,6 +404,7 @@ pub(crate) async fn install( let mut errors = vec![]; let mut downloaded = Vec::with_capacity(downloads.len()); + let mut requests_by_new_installation = BTreeMap::new(); while let Some((download, result)) = tasks.next().await { match result { Ok(download_result) => { @@ -412,10 +416,19 @@ pub(crate) async fn install( let installation = ManagedPythonInstallation::new(path, download); changelog.installed.insert(installation.key().clone()); + for request in &requests { + // Take note of which installations satisfied which requests + if request.matches_installation(&installation) { + requests_by_new_installation + .entry(installation.key().clone()) + .or_insert(Vec::new()) + .push(request); + } + } if changelog.existing.contains(installation.key()) { changelog.uninstalled.insert(installation.key().clone()); } - downloaded.push(installation); + downloaded.push(installation.clone()); } Err(err) => { errors.push((download.key().clone(), anyhow::Error::new(err))); @@ -529,6 +542,42 @@ pub(crate) async fn install( } if !changelog.installed.is_empty() { + for install_key in &changelog.installed { + // Make a note if the selected python is non-native for the architecture, + // if none of the matching user requests were explicit + let native_arch = Arch::from_env(); + if install_key.arch().family() != native_arch.family() { + let not_explicit = + requests_by_new_installation + .get(install_key) + .and_then(|requests| { + let all_non_explicit = requests.iter().all(|request| { + if let PythonRequest::Key(key) = &request.request { + !matches!(key.arch(), Some(ArchRequest::Explicit(_))) + } else { + true + } + }); + if all_non_explicit { + requests.iter().next() + } else { + None + } + }); + if let Some(not_explicit) = not_explicit { + let native_request = + not_explicit.download_request.clone().with_arch(native_arch); + writeln!( + printer.stderr(), + "{} uv selected a Python distribution with an emulated architecture ({}) for your platform because support for the native architecture ({}) is not yet mature; to override this behaviour, request the native architecture explicitly with: {}", + "note:".bold(), + install_key.arch(), + native_arch, + native_request + )?; + } + } + } if changelog.installed.len() == 1 { let installed = changelog.installed.iter().next().unwrap(); // Ex) "Installed Python 3.9.7 in 1.68s"