Split platform detection code into a dedicated uv-platform crate (#14918)
Some checks are pending
CI / check system | alpine (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions

In service of some subsequent work...
This commit is contained in:
Zanie Blue 2025-07-28 14:12:04 -05:00 committed by GitHub
parent 5686771464
commit 00efde06b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 530 additions and 469 deletions

View file

@ -1,33 +0,0 @@
//! Fetches CPU information.
use anyhow::Error;
#[cfg(target_os = "linux")]
use procfs::{CpuInfo, Current};
/// Detects whether the hardware supports floating-point operations using ARM's Vector Floating Point (VFP) hardware.
///
/// This function is relevant specifically for ARM architectures, where the presence of the "vfp" flag in `/proc/cpuinfo`
/// indicates that the CPU supports hardware floating-point operations.
/// This helps determine whether the system is using the `gnueabihf` (hard-float) ABI or `gnueabi` (soft-float) ABI.
///
/// More information on this can be found in the [Debian ARM Hard Float Port documentation](https://wiki.debian.org/ArmHardFloatPort#VFP).
#[cfg(target_os = "linux")]
pub(crate) fn detect_hardware_floating_point_support() -> Result<bool, Error> {
let cpu_info = CpuInfo::current()?;
if let Some(features) = cpu_info.fields.get("Features") {
if features.contains("vfp") {
return Ok(true); // "vfp" found: hard-float (gnueabihf) detected
}
}
Ok(false) // Default to soft-float (gnueabi) if no "vfp" flag is found
}
/// For non-Linux systems or architectures, the function will return `false` as hardware floating-point detection
/// is not applicable outside of Linux ARM architectures.
#[cfg(not(target_os = "linux"))]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn detect_hardware_floating_point_support() -> Result<bool, Error> {
Ok(false) // Non-Linux or non-ARM systems: hardware floating-point detection is not applicable
}

View file

@ -3066,8 +3066,8 @@ mod tests {
discovery::{PythonRequest, VersionRequest},
downloads::{ArchRequest, PythonDownloadRequest},
implementation::ImplementationName,
platform::{Arch, Libc, Os},
};
use uv_platform::{Arch, Libc, Os};
use super::{Error, PythonVariant};
@ -3154,11 +3154,11 @@ mod tests {
PythonVariant::Default
)),
implementation: Some(ImplementationName::CPython),
arch: Some(ArchRequest::Explicit(Arch {
family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
variant: None
})),
os: Some(Os(target_lexicon::OperatingSystem::Darwin(None))),
arch: Some(ArchRequest::Explicit(Arch::new(
Architecture::Aarch64(Aarch64Architecture::Aarch64),
None
))),
os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
libc: Some(Libc::None),
prereleases: None
})
@ -3189,10 +3189,10 @@ mod tests {
PythonVariant::Default
)),
implementation: None,
arch: Some(ArchRequest::Explicit(Arch {
family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
variant: None
})),
arch: Some(ArchRequest::Explicit(Arch::new(
Architecture::Aarch64(Aarch64Architecture::Aarch64),
None
))),
os: None,
libc: None,
prereleases: None

View file

@ -25,6 +25,7 @@ use uv_client::{BaseClient, WrappedReqwestError, is_extended_transient_error};
use uv_distribution_filename::{ExtensionError, SourceDistExtension};
use uv_extract::hash::Hasher;
use uv_fs::{Simplified, rename_with_retry};
use uv_platform::{self as platform, Arch, Libc, Os};
use uv_pypi_types::{HashAlgorithm, HashDigest};
use uv_redacted::DisplaySafeUrl;
use uv_static::EnvVars;
@ -34,9 +35,7 @@ use crate::implementation::{
Error as ImplementationError, ImplementationName, LenientImplementationName,
};
use crate::installation::PythonInstallationKey;
use crate::libc::LibcDetectionError;
use crate::managed::ManagedPythonInstallation;
use crate::platform::{self, Arch, Libc, Os};
use crate::{Interpreter, PythonRequest, PythonVersion, VersionRequest};
#[derive(Error, Debug)]
@ -98,7 +97,7 @@ pub enum Error {
#[error("A mirror was provided via `{0}`, but the URL does not match the expected format: {0}")]
Mirror(&'static str, &'static str),
#[error("Failed to determine the libc used on the current platform")]
LibcDetection(#[from] LibcDetectionError),
LibcDetection(#[from] platform::LibcDetectionError),
#[error("Remote Python downloads JSON is not yet supported, please use a local path")]
RemoteJSONNotSupported,
#[error("The JSON of the python downloads is invalid: {0}")]

View file

@ -10,6 +10,7 @@ use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::Preview;
use uv_pep440::{Prerelease, Version};
use uv_platform::{Arch, Libc, Os};
use crate::discovery::{
EnvironmentPreference, PythonRequest, find_best_python_installation, find_python_installation,
@ -17,7 +18,6 @@ use crate::discovery::{
use crate::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest, Reporter};
use crate::implementation::LenientImplementationName;
use crate::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
use crate::platform::{Arch, Libc, Os};
use crate::{
Error, ImplementationName, Interpreter, PythonDownloads, PythonPreference, PythonSource,
PythonVariant, PythonVersion, downloads,

View file

@ -21,13 +21,13 @@ use uv_fs::{LockedFile, PythonExt, Simplified, write_atomic_sync};
use uv_install_wheel::Layout;
use uv_pep440::Version;
use uv_pep508::{MarkerEnvironment, StringVersion};
use uv_platform::{Arch, Libc, Os};
use uv_platform_tags::Platform;
use uv_platform_tags::{Tags, TagsError};
use uv_pypi_types::{ResolverMarkerEnvironment, Scheme};
use crate::implementation::LenientImplementationName;
use crate::managed::ManagedPythonInstallations;
use crate::platform::{Arch, Libc, Os};
use crate::pointer_size::PointerSize;
use crate::{
Prefix, PythonInstallationKey, PythonVariant, PythonVersion, Target, VersionRequest,

View file

@ -29,19 +29,16 @@ pub use crate::version_files::{
};
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
mod cpuinfo;
mod discovery;
pub mod downloads;
mod environment;
mod implementation;
mod installation;
mod interpreter;
mod libc;
pub mod macos_dylib;
pub mod managed;
#[cfg(windows)]
mod microsoft_store;
pub mod platform;
mod pointer_size;
mod prefix;
mod python_version;

View file

@ -1,303 +0,0 @@
//! Determine the libc (glibc or musl) on linux.
//!
//! Taken from `glibc_version` (<https://github.com/delta-incubator/glibc-version-rs>),
//! which used the Apache 2.0 license (but not the MIT license)
use fs_err as fs;
use goblin::elf::Elf;
use regex::Regex;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::LazyLock;
use thiserror::Error;
use tracing::trace;
use uv_fs::Simplified;
#[derive(Debug, Error)]
pub enum LibcDetectionError {
#[error(
"Could not detect either glibc version nor musl libc version, at least one of which is required"
)]
NoLibcFound,
#[error("Failed to get base name of symbolic link path {0}")]
MissingBasePath(PathBuf),
#[error("Failed to find glibc version in the filename of linker: `{0}`")]
GlibcExtractionMismatch(PathBuf),
#[error("Failed to determine {libc} version by running: `{program}`")]
FailedToRun {
libc: &'static str,
program: String,
#[source]
err: io::Error,
},
#[error("Could not find glibc version in output of: `{0} --version`")]
InvalidLdSoOutputGnu(PathBuf),
#[error("Could not find musl version in output of: `{0}`")]
InvalidLdSoOutputMusl(PathBuf),
#[error("Could not read ELF interpreter from any of the following paths: {0}")]
CoreBinaryParsing(String),
#[error("Failed to find any common binaries to determine libc from: {0}")]
NoCommonBinariesFound(String),
#[error("Failed to determine libc")]
Io(#[from] io::Error),
}
/// We support glibc (manylinux) and musl (musllinux) on linux.
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum LibcVersion {
Manylinux { major: u32, minor: u32 },
Musllinux { major: u32, minor: u32 },
}
/// Determine whether we're running glibc or musl and in which version, given we are on linux.
///
/// Normally, we determine this from the python interpreter, which is more accurate, but when
/// deciding which python interpreter to download, we need to figure this out from the environment.
///
/// A platform can have both musl and glibc installed. We determine the preferred platform by
/// inspecting core binaries.
pub(crate) fn detect_linux_libc() -> Result<LibcVersion, LibcDetectionError> {
let ld_path = find_ld_path()?;
trace!("Found `ld` path: {}", ld_path.user_display());
match detect_musl_version(&ld_path) {
Ok(os) => return Ok(os),
Err(err) => {
trace!("Tried to find musl version by running `{ld_path:?}`, but failed: {err}");
}
}
match detect_linux_libc_from_ld_symlink(&ld_path) {
Ok(os) => return Ok(os),
Err(err) => {
trace!(
"Tried to find libc version from possible symlink at {ld_path:?}, but failed: {err}"
);
}
}
match detect_glibc_version_from_ld(&ld_path) {
Ok(os_version) => return Ok(os_version),
Err(err) => {
trace!(
"Tried to find glibc version from `{} --version`, but failed: {}",
ld_path.simplified_display(),
err
);
}
}
Err(LibcDetectionError::NoLibcFound)
}
// glibc version is taken from `std/sys/unix/os.rs`.
fn detect_glibc_version_from_ld(ld_so: &Path) -> Result<LibcVersion, LibcDetectionError> {
let output = Command::new(ld_so)
.args(["--version"])
.output()
.map_err(|err| LibcDetectionError::FailedToRun {
libc: "glibc",
program: format!("{} --version", ld_so.user_display()),
err,
})?;
if let Some(os) = glibc_ld_output_to_version("stdout", &output.stdout) {
return Ok(os);
}
if let Some(os) = glibc_ld_output_to_version("stderr", &output.stderr) {
return Ok(os);
}
Err(LibcDetectionError::InvalidLdSoOutputGnu(
ld_so.to_path_buf(),
))
}
/// Parse output `/lib64/ld-linux-x86-64.so.2 --version` and equivalent ld.so files.
///
/// Example: `ld.so (Ubuntu GLIBC 2.39-0ubuntu8.3) stable release version 2.39.`.
fn glibc_ld_output_to_version(kind: &str, output: &[u8]) -> Option<LibcVersion> {
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"ld.so \(.+\) .* ([0-9]+\.[0-9]+)").unwrap());
let output = String::from_utf8_lossy(output);
trace!("{kind} output from `ld.so --version`: {output:?}");
let (_, [version]) = RE.captures(output.as_ref()).map(|c| c.extract())?;
// Parse the input as "x.y" glibc version.
let mut parsed_ints = version.split('.').map(str::parse).fuse();
let major = parsed_ints.next()?.ok()?;
let minor = parsed_ints.next()?.ok()?;
trace!("Found manylinux {major}.{minor} in {kind} of ld.so version");
Some(LibcVersion::Manylinux { major, minor })
}
fn detect_linux_libc_from_ld_symlink(path: &Path) -> Result<LibcVersion, LibcDetectionError> {
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^ld-([0-9]{1,3})\.([0-9]{1,3})\.so$").unwrap());
let ld_path = fs::read_link(path)?;
let filename = ld_path
.file_name()
.ok_or_else(|| LibcDetectionError::MissingBasePath(ld_path.clone()))?
.to_string_lossy();
let (_, [major, minor]) = RE
.captures(&filename)
.map(|c| c.extract())
.ok_or_else(|| LibcDetectionError::GlibcExtractionMismatch(ld_path.clone()))?;
// OK since we are guaranteed to have between 1 and 3 ASCII digits and the
// maximum possible value, 999, fits into a u16.
let major = major.parse().expect("valid major version");
let minor = minor.parse().expect("valid minor version");
Ok(LibcVersion::Manylinux { major, minor })
}
/// Read the musl version from libc library's output. Taken from maturin.
///
/// The libc library should output something like this to `stderr`:
///
/// ```text
/// musl libc (`x86_64`)
/// Version 1.2.2
/// Dynamic Program Loader
/// ```
fn detect_musl_version(ld_path: impl AsRef<Path>) -> Result<LibcVersion, LibcDetectionError> {
let ld_path = ld_path.as_ref();
let output = Command::new(ld_path)
.stdout(Stdio::null())
.stderr(Stdio::piped())
.output()
.map_err(|err| LibcDetectionError::FailedToRun {
libc: "musl",
program: ld_path.to_string_lossy().to_string(),
err,
})?;
if let Some(os) = musl_ld_output_to_version("stdout", &output.stdout) {
return Ok(os);
}
if let Some(os) = musl_ld_output_to_version("stderr", &output.stderr) {
return Ok(os);
}
Err(LibcDetectionError::InvalidLdSoOutputMusl(
ld_path.to_path_buf(),
))
}
/// Parse the musl version from ld output.
///
/// Example: `Version 1.2.5`.
fn musl_ld_output_to_version(kind: &str, output: &[u8]) -> Option<LibcVersion> {
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"Version ([0-9]{1,4})\.([0-9]{1,4})").unwrap());
let output = String::from_utf8_lossy(output);
trace!("{kind} output from `ld`: {output:?}");
let (_, [major, minor]) = RE.captures(output.as_ref()).map(|c| c.extract())?;
// unwrap-safety: Since we are guaranteed to have between 1 and 4 ASCII digits and the
// maximum possible value, 9999, fits into a u16.
let major = major.parse().expect("valid major version");
let minor = minor.parse().expect("valid minor version");
trace!("Found musllinux {major}.{minor} in {kind} of `ld`");
Some(LibcVersion::Musllinux { major, minor })
}
/// Find musl ld path from executable's ELF header.
fn find_ld_path() -> Result<PathBuf, LibcDetectionError> {
// At first, we just looked for /bin/ls. But on some Linux distros, /bin/ls
// is a shell script that just calls /usr/bin/ls. So we switched to looking
// at /bin/sh. But apparently in some environments, /bin/sh is itself just
// a shell script that calls /bin/dash. So... We just try a few different
// paths. In most cases, /bin/sh should work.
//
// See: https://github.com/astral-sh/uv/pull/1493
// See: https://github.com/astral-sh/uv/issues/1810
// See: https://github.com/astral-sh/uv/issues/4242#issuecomment-2306164449
let attempts = ["/bin/sh", "/usr/bin/env", "/bin/dash", "/bin/ls"];
let mut found_anything = false;
for path in attempts {
if std::fs::exists(path).ok() == Some(true) {
found_anything = true;
if let Some(ld_path) = find_ld_path_at(path) {
return Ok(ld_path);
}
}
}
let attempts_string = attempts.join(", ");
if !found_anything {
// Known failure cases here include running the distroless Docker images directly
// (depending on what subcommand you use) and certain Nix setups. See:
// https://github.com/astral-sh/uv/issues/8635
Err(LibcDetectionError::NoCommonBinariesFound(attempts_string))
} else {
Err(LibcDetectionError::CoreBinaryParsing(attempts_string))
}
}
/// Attempt to find the path to the `ld` executable by
/// ELF parsing the given path. If this fails for any
/// reason, then an error is returned.
fn find_ld_path_at(path: impl AsRef<Path>) -> Option<PathBuf> {
let path = path.as_ref();
// Not all linux distributions have all of these paths.
let buffer = fs::read(path).ok()?;
let elf = match Elf::parse(&buffer) {
Ok(elf) => elf,
Err(err) => {
trace!(
"Could not parse ELF file at `{}`: `{}`",
path.user_display(),
err
);
return None;
}
};
let Some(elf_interpreter) = elf.interpreter else {
trace!(
"Couldn't find ELF interpreter path from {}",
path.user_display()
);
return None;
};
Some(PathBuf::from(elf_interpreter))
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn parse_ld_so_output() {
let ver_str = glibc_ld_output_to_version(
"stdout",
indoc! {br"ld.so (Ubuntu GLIBC 2.39-0ubuntu8.3) stable release version 2.39.
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
"},
)
.unwrap();
assert_eq!(
ver_str,
LibcVersion::Manylinux {
major: 2,
minor: 39
}
);
}
#[test]
fn parse_musl_ld_output() {
// This output was generated by running `/lib/ld-musl-x86_64.so.1`
// in an Alpine Docker image. The Alpine version:
//
// # cat /etc/alpine-release
// 3.19.1
let output = b"\
musl libc (x86_64)
Version 1.2.4_git20230717
Dynamic Program Loader
Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args]\
";
let got = musl_ld_output_to_version("stderr", output).unwrap();
assert_eq!(got, LibcVersion::Musllinux { major: 1, minor: 2 });
}
}

View file

@ -17,6 +17,8 @@ use uv_configuration::{Preview, PreviewFeatures};
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
use uv_platform::Error as PlatformError;
use uv_platform::{Arch, Libc, LibcDetectionError, Os};
use uv_state::{StateBucket, StateStore};
use uv_static::EnvVars;
use uv_trampoline_builder::{Launcher, windows_python_launcher};
@ -26,9 +28,6 @@ use crate::implementation::{
Error as ImplementationError, ImplementationName, LenientImplementationName,
};
use crate::installation::{self, PythonInstallationKey};
use crate::libc::LibcDetectionError;
use crate::platform::Error as PlatformError;
use crate::platform::{Arch, Libc, Os};
use crate::python_version::PythonVersion;
use crate::{
PythonInstallationMinorVersionKey, PythonRequest, PythonVariant, macos_dylib, sysconfig,
@ -271,7 +270,7 @@ impl ManagedPythonInstallations {
&& (arch.supports(installation.key.arch)
// TODO(zanieb): Allow inequal variants, as `Arch::supports` does not
// implement this yet. See https://github.com/astral-sh/uv/pull/9788
|| arch.family == installation.key.arch.family)
|| arch.family() == installation.key.arch.family())
&& installation.key.libc == libc
});
@ -545,7 +544,7 @@ impl ManagedPythonInstallation {
/// standard `EXTERNALLY-MANAGED` file.
pub fn ensure_externally_managed(&self) -> Result<(), Error> {
// Construct the path to the `stdlib` directory.
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
let stdlib = if self.key.os.is_windows() {
self.python_dir().join("Lib")
} else {
let lib_suffix = self.key.variant.suffix();

View file

@ -1,427 +0,0 @@
use crate::cpuinfo::detect_hardware_floating_point_support;
use crate::libc::{LibcDetectionError, LibcVersion, detect_linux_libc};
use std::fmt::Display;
use std::ops::Deref;
use std::{fmt, str::FromStr};
use thiserror::Error;
use uv_static::EnvVars;
#[derive(Error, Debug)]
pub enum Error {
#[error("Unknown operating system: {0}")]
UnknownOs(String),
#[error("Unknown architecture: {0}")]
UnknownArch(String),
#[error("Unknown libc environment: {0}")]
UnknownLibc(String),
#[error("Unsupported variant `{0}` for architecture `{1}`")]
UnsupportedVariant(String, String),
#[error(transparent)]
LibcDetectionError(#[from] LibcDetectionError),
}
/// Architecture variants, e.g., with support for different instruction sets
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
pub enum ArchVariant {
/// Targets 64-bit Intel/AMD CPUs newer than Nehalem (2008).
/// Includes SSE3, SSE4 and other post-2003 CPU instructions.
V2,
/// Targets 64-bit Intel/AMD CPUs newer than Haswell (2013) and Excavator (2015).
/// Includes AVX, AVX2, MOVBE and other newer CPU instructions.
V3,
/// Targets 64-bit Intel/AMD CPUs with AVX-512 instructions (post-2017 Intel CPUs).
/// Many post-2017 Intel CPUs do not support AVX-512.
V4,
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub struct Arch {
pub(crate) family: target_lexicon::Architecture,
pub(crate) variant: Option<ArchVariant>,
}
impl Ord for Arch {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.family == other.family {
return self.variant.cmp(&other.variant);
}
// 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()
};
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-preferred, fallback to lexicographic order
self.family.to_string().cmp(&other.family.to_string())
}
}
}
}
impl PartialOrd for Arch {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub struct Os(pub(crate) target_lexicon::OperatingSystem);
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum Libc {
Some(target_lexicon::Environment),
None,
}
impl Libc {
pub(crate) fn from_env() -> Result<Self, Error> {
match std::env::consts::OS {
"linux" => {
if let Ok(libc) = std::env::var(EnvVars::UV_LIBC) {
if !libc.is_empty() {
return Self::from_str(&libc);
}
}
Ok(Self::Some(match detect_linux_libc()? {
LibcVersion::Manylinux { .. } => match std::env::consts::ARCH {
// Checks if the CPU supports hardware floating-point operations.
// Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment.
// download-metadata.json only includes armv7.
"arm" | "armv5te" | "armv7" => {
match detect_hardware_floating_point_support() {
Ok(true) => target_lexicon::Environment::Gnueabihf,
Ok(false) => target_lexicon::Environment::Gnueabi,
Err(_) => target_lexicon::Environment::Gnu,
}
}
_ => target_lexicon::Environment::Gnu,
},
LibcVersion::Musllinux { .. } => target_lexicon::Environment::Musl,
}))
}
"windows" | "macos" => Ok(Self::None),
// Use `None` on platforms without explicit support.
_ => Ok(Self::None),
}
}
pub fn is_musl(&self) -> bool {
matches!(self, Self::Some(target_lexicon::Environment::Musl))
}
}
impl FromStr for Libc {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"gnu" => Ok(Self::Some(target_lexicon::Environment::Gnu)),
"gnueabi" => Ok(Self::Some(target_lexicon::Environment::Gnueabi)),
"gnueabihf" => Ok(Self::Some(target_lexicon::Environment::Gnueabihf)),
"musl" => Ok(Self::Some(target_lexicon::Environment::Musl)),
"none" => Ok(Self::None),
_ => Err(Error::UnknownLibc(s.to_string())),
}
}
}
impl Os {
pub fn from_env() -> Self {
Self(target_lexicon::HOST.operating_system)
}
}
impl Arch {
pub fn from_env() -> Self {
Self {
family: target_lexicon::HOST.architecture,
variant: None,
}
}
/// Does the current architecture support running the other?
///
/// When the architecture is equal, this is always true. Otherwise, this is true if the
/// architecture is transparently emulated or is a microarchitecture with worse performance
/// characteristics.
pub(crate) fn supports(self, other: Self) -> bool {
if self == other {
return true;
}
// TODO: Implement `variant` support checks
// Windows ARM64 runs emulated x86_64 binaries transparently
// Similarly, macOS aarch64 runs emulated x86_64 binaries transparently if you have Rosetta
// installed. We don't try to be clever and check if that's the case here, we just assume
// that if x86_64 distributions are available, they're usable.
if (cfg!(windows) || cfg!(target_os = "macos"))
&& matches!(self.family, target_lexicon::Architecture::Aarch64(_))
{
return other.family == target_lexicon::Architecture::X86_64;
}
false
}
pub fn family(&self) -> target_lexicon::Architecture {
self.family
}
pub fn is_arm(&self) -> bool {
matches!(self.family, target_lexicon::Architecture::Arm(_))
}
}
impl Display for Libc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Some(env) => write!(f, "{env}"),
Self::None => write!(f, "none"),
}
}
}
impl Display for Os {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &**self {
target_lexicon::OperatingSystem::Darwin(_) => write!(f, "macos"),
inner => write!(f, "{inner}"),
}
}
}
impl Display for Arch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.family {
target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
write!(f, "x86")?;
}
inner => write!(f, "{inner}")?,
}
if let Some(variant) = self.variant {
write!(f, "_{variant}")?;
}
Ok(())
}
}
impl FromStr for Os {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let inner = match s {
"macos" => target_lexicon::OperatingSystem::Darwin(None),
_ => target_lexicon::OperatingSystem::from_str(s)
.map_err(|()| Error::UnknownOs(s.to_string()))?,
};
if matches!(inner, target_lexicon::OperatingSystem::Unknown) {
return Err(Error::UnknownOs(s.to_string()));
}
Ok(Self(inner))
}
}
impl FromStr for Arch {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn parse_family(s: &str) -> Result<target_lexicon::Architecture, Error> {
let inner = match s {
// Allow users to specify "x86" as a shorthand for the "i686" variant, they should not need
// to specify the exact architecture and this variant is what we have downloads for.
"x86" => {
target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)
}
_ => target_lexicon::Architecture::from_str(s)
.map_err(|()| Error::UnknownArch(s.to_string()))?,
};
if matches!(inner, target_lexicon::Architecture::Unknown) {
return Err(Error::UnknownArch(s.to_string()));
}
Ok(inner)
}
// First check for a variant
if let Some((Ok(family), Ok(variant))) = s
.rsplit_once('_')
.map(|(family, variant)| (parse_family(family), ArchVariant::from_str(variant)))
{
// We only support variants for `x86_64` right now
if !matches!(family, target_lexicon::Architecture::X86_64) {
return Err(Error::UnsupportedVariant(
variant.to_string(),
family.to_string(),
));
}
return Ok(Self {
family,
variant: Some(variant),
});
}
let family = parse_family(s)?;
Ok(Self {
family,
variant: None,
})
}
}
impl FromStr for ArchVariant {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"v2" => Ok(Self::V2),
"v3" => Ok(Self::V3),
"v4" => Ok(Self::V4),
_ => Err(()),
}
}
}
impl Display for ArchVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::V2 => write!(f, "v2"),
Self::V3 => write!(f, "v3"),
Self::V4 => write!(f, "v4"),
}
}
}
impl Deref for Os {
type Target = target_lexicon::OperatingSystem;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&uv_platform_tags::Arch> for Arch {
fn from(value: &uv_platform_tags::Arch) -> Self {
match value {
uv_platform_tags::Arch::Aarch64 => Self {
family: target_lexicon::Architecture::Aarch64(
target_lexicon::Aarch64Architecture::Aarch64,
),
variant: None,
},
uv_platform_tags::Arch::Armv5TEL => Self {
family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te),
variant: None,
},
uv_platform_tags::Arch::Armv6L => Self {
family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6),
variant: None,
},
uv_platform_tags::Arch::Armv7L => Self {
family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7),
variant: None,
},
uv_platform_tags::Arch::S390X => Self {
family: target_lexicon::Architecture::S390x,
variant: None,
},
uv_platform_tags::Arch::Powerpc => Self {
family: target_lexicon::Architecture::Powerpc,
variant: None,
},
uv_platform_tags::Arch::Powerpc64 => Self {
family: target_lexicon::Architecture::Powerpc64,
variant: None,
},
uv_platform_tags::Arch::Powerpc64Le => Self {
family: target_lexicon::Architecture::Powerpc64le,
variant: None,
},
uv_platform_tags::Arch::X86 => Self {
family: target_lexicon::Architecture::X86_32(
target_lexicon::X86_32Architecture::I686,
),
variant: None,
},
uv_platform_tags::Arch::X86_64 => Self {
family: target_lexicon::Architecture::X86_64,
variant: None,
},
uv_platform_tags::Arch::LoongArch64 => Self {
family: target_lexicon::Architecture::LoongArch64,
variant: None,
},
uv_platform_tags::Arch::Riscv64 => Self {
family: target_lexicon::Architecture::Riscv64(
target_lexicon::Riscv64Architecture::Riscv64,
),
variant: None,
},
uv_platform_tags::Arch::Wasm32 => Self {
family: target_lexicon::Architecture::Wasm32,
variant: None,
},
}
}
}
impl From<&uv_platform_tags::Os> for Libc {
fn from(value: &uv_platform_tags::Os) -> Self {
match value {
uv_platform_tags::Os::Manylinux { .. } => Self::Some(target_lexicon::Environment::Gnu),
uv_platform_tags::Os::Musllinux { .. } => Self::Some(target_lexicon::Environment::Musl),
_ => Self::None,
}
}
}
impl From<&uv_platform_tags::Os> for Os {
fn from(value: &uv_platform_tags::Os) -> Self {
match value {
uv_platform_tags::Os::Dragonfly { .. } => {
Self(target_lexicon::OperatingSystem::Dragonfly)
}
uv_platform_tags::Os::FreeBsd { .. } => Self(target_lexicon::OperatingSystem::Freebsd),
uv_platform_tags::Os::Haiku { .. } => Self(target_lexicon::OperatingSystem::Haiku),
uv_platform_tags::Os::Illumos { .. } => Self(target_lexicon::OperatingSystem::Illumos),
uv_platform_tags::Os::Macos { .. } => {
Self(target_lexicon::OperatingSystem::Darwin(None))
}
uv_platform_tags::Os::Manylinux { .. }
| uv_platform_tags::Os::Musllinux { .. }
| uv_platform_tags::Os::Android { .. } => Self(target_lexicon::OperatingSystem::Linux),
uv_platform_tags::Os::NetBsd { .. } => Self(target_lexicon::OperatingSystem::Netbsd),
uv_platform_tags::Os::OpenBsd { .. } => Self(target_lexicon::OperatingSystem::Openbsd),
uv_platform_tags::Os::Windows => Self(target_lexicon::OperatingSystem::Windows),
uv_platform_tags::Os::Pyodide { .. } => {
Self(target_lexicon::OperatingSystem::Emscripten)
}
}
}
}

View file

@ -1,7 +1,6 @@
//! PEP 514 interactions with the Windows registry.
use crate::managed::ManagedPythonInstallation;
use crate::platform::Arch;
use crate::{COMPANY_DISPLAY_NAME, COMPANY_KEY, PythonInstallationKey, PythonVersion};
use anyhow::anyhow;
use std::cmp::Ordering;
@ -11,6 +10,7 @@ use std::str::FromStr;
use target_lexicon::PointerWidth;
use thiserror::Error;
use tracing::debug;
use uv_platform::Arch;
use uv_warnings::{warn_user, warn_user_once};
use windows_registry::{CURRENT_USER, HSTRING, Key, LOCAL_MACHINE, Value};
use windows_result::HRESULT;