mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-10-31 03:55:33 +00:00 
			
		
		
		
	 c6713f5751
			
		
	
	
		c6713f5751
		
			
		
	
	
	
		
			
	
		
	
	
		
			Some checks are pending
		
		
	
	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 shear (push) Waiting to run
				
			CI / cargo test | ubuntu (push) Blocked by required conditions
				
			CI / cargo test | macos (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 | i686 (push) Blocked by required conditions
				
			CI / smoke test | windows x86_64 (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 / mkdocs (push) Waiting to run
				
			CI / build binary | linux libc (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 / cargo build (msrv) (push) Blocked by required conditions
				
			CI / build binary | freebsd (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 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 linux (push) Blocked by required conditions
				
			CI / integration test | free-threaded on windows (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 | github actions (push) Blocked by required conditions
				
			CI / integration test | determine publish changes (push) Blocked by required conditions
				
			CI / integration test | uv publish (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 opensuse (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 | pypy on ubuntu (push) Blocked by required conditions
				
			CI / check system | pyston (push) Blocked by required conditions
				
			CI / check system | alpine (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 | 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 (push) Blocked by required conditions
				
			
		
			
				
	
	
		
			262 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| 
 | |
| import collections
 | |
| import contextlib
 | |
| import functools
 | |
| import os
 | |
| import re
 | |
| import sys
 | |
| import warnings
 | |
| from typing import Generator, Iterator, NamedTuple, Sequence
 | |
| 
 | |
| from ._elffile import EIClass, EIData, ELFFile, EMachine
 | |
| 
 | |
| EF_ARM_ABIMASK = 0xFF000000
 | |
| EF_ARM_ABI_VER5 = 0x05000000
 | |
| EF_ARM_ABI_FLOAT_HARD = 0x00000400
 | |
| 
 | |
| 
 | |
| # `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
 | |
| # as the type for `path` until then.
 | |
| @contextlib.contextmanager
 | |
| def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
 | |
|     try:
 | |
|         with open(path, "rb") as f:
 | |
|             yield ELFFile(f)
 | |
|     except (OSError, TypeError, ValueError):
 | |
|         yield None
 | |
| 
 | |
| 
 | |
| def _is_linux_armhf(executable: str) -> bool:
 | |
|     # hard-float ABI can be detected from the ELF header of the running
 | |
|     # process
 | |
|     # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
 | |
|     with _parse_elf(executable) as f:
 | |
|         return (
 | |
|             f is not None
 | |
|             and f.capacity == EIClass.C32
 | |
|             and f.encoding == EIData.Lsb
 | |
|             and f.machine == EMachine.Arm
 | |
|             and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
 | |
|             and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
 | |
|         )
 | |
| 
 | |
| 
 | |
| def _is_linux_i686(executable: str) -> bool:
 | |
|     with _parse_elf(executable) as f:
 | |
|         return (
 | |
|             f is not None
 | |
|             and f.capacity == EIClass.C32
 | |
|             and f.encoding == EIData.Lsb
 | |
|             and f.machine == EMachine.I386
 | |
|         )
 | |
| 
 | |
| 
 | |
| def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
 | |
|     if "armv7l" in archs:
 | |
|         return _is_linux_armhf(executable)
 | |
|     if "i686" in archs:
 | |
|         return _is_linux_i686(executable)
 | |
|     allowed_archs = {
 | |
|         "x86_64",
 | |
|         "aarch64",
 | |
|         "ppc64",
 | |
|         "ppc64le",
 | |
|         "s390x",
 | |
|         "loongarch64",
 | |
|         "riscv64",
 | |
|     }
 | |
|     return any(arch in allowed_archs for arch in archs)
 | |
| 
 | |
| 
 | |
| # If glibc ever changes its major version, we need to know what the last
 | |
| # minor version was, so we can build the complete list of all versions.
 | |
| # For now, guess what the highest minor version might be, assume it will
 | |
| # be 50 for testing. Once this actually happens, update the dictionary
 | |
| # with the actual value.
 | |
| _LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
 | |
| 
 | |
| 
 | |
| class _GLibCVersion(NamedTuple):
 | |
|     major: int
 | |
|     minor: int
 | |
| 
 | |
| 
 | |
| def _glibc_version_string_confstr() -> str | None:
 | |
|     """
 | |
|     Primary implementation of glibc_version_string using os.confstr.
 | |
|     """
 | |
|     # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
 | |
|     # to be broken or missing. This strategy is used in the standard library
 | |
|     # platform module.
 | |
|     # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
 | |
|     try:
 | |
|         # Should be a string like "glibc 2.17".
 | |
|         version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
 | |
|         assert version_string is not None
 | |
|         _, version = version_string.rsplit()
 | |
|     except (AssertionError, AttributeError, OSError, ValueError):
 | |
|         # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
 | |
|         return None
 | |
|     return version
 | |
| 
 | |
| 
 | |
| def _glibc_version_string_ctypes() -> str | None:
 | |
|     """
 | |
|     Fallback implementation of glibc_version_string using ctypes.
 | |
|     """
 | |
|     try:
 | |
|         import ctypes
 | |
|     except ImportError:
 | |
|         return None
 | |
| 
 | |
|     # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
 | |
|     # manpage says, "If filename is NULL, then the returned handle is for the
 | |
|     # main program". This way we can let the linker do the work to figure out
 | |
|     # which libc our process is actually using.
 | |
|     #
 | |
|     # We must also handle the special case where the executable is not a
 | |
|     # dynamically linked executable. This can occur when using musl libc,
 | |
|     # for example. In this situation, dlopen() will error, leading to an
 | |
|     # OSError. Interestingly, at least in the case of musl, there is no
 | |
|     # errno set on the OSError. The single string argument used to construct
 | |
|     # OSError comes from libc itself and is therefore not portable to
 | |
|     # hard code here. In any case, failure to call dlopen() means we
 | |
|     # can proceed, so we bail on our attempt.
 | |
|     try:
 | |
|         process_namespace = ctypes.CDLL(None)
 | |
|     except OSError:
 | |
|         return None
 | |
| 
 | |
|     try:
 | |
|         gnu_get_libc_version = process_namespace.gnu_get_libc_version
 | |
|     except AttributeError:
 | |
|         # Symbol doesn't exist -> therefore, we are not linked to
 | |
|         # glibc.
 | |
|         return None
 | |
| 
 | |
|     # Call gnu_get_libc_version, which returns a string like "2.5"
 | |
|     gnu_get_libc_version.restype = ctypes.c_char_p
 | |
|     version_str: str = gnu_get_libc_version()
 | |
|     # py2 / py3 compatibility:
 | |
|     if not isinstance(version_str, str):
 | |
|         version_str = version_str.decode("ascii")
 | |
| 
 | |
|     return version_str
 | |
| 
 | |
| 
 | |
| def _glibc_version_string() -> str | None:
 | |
|     """Returns glibc version string, or None if not using glibc."""
 | |
|     return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
 | |
| 
 | |
| 
 | |
| def _parse_glibc_version(version_str: str) -> _GLibCVersion:
 | |
|     """Parse glibc version.
 | |
| 
 | |
|     We use a regexp instead of str.split because we want to discard any
 | |
|     random junk that might come after the minor version -- this might happen
 | |
|     in patched/forked versions of glibc (e.g. Linaro's version of glibc
 | |
|     uses version strings like "2.20-2014.11"). See gh-3588.
 | |
|     """
 | |
|     m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
 | |
|     if not m:
 | |
|         warnings.warn(
 | |
|             f"Expected glibc version with 2 components major.minor,"
 | |
|             f" got: {version_str}",
 | |
|             RuntimeWarning,
 | |
|         )
 | |
|         return _GLibCVersion(-1, -1)
 | |
|     return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
 | |
| 
 | |
| 
 | |
| @functools.lru_cache()
 | |
| def _get_glibc_version() -> _GLibCVersion:
 | |
|     version_str = _glibc_version_string()
 | |
|     if version_str is None:
 | |
|         return _GLibCVersion(-1, -1)
 | |
|     return _parse_glibc_version(version_str)
 | |
| 
 | |
| 
 | |
| # From PEP 513, PEP 600
 | |
| def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
 | |
|     sys_glibc = _get_glibc_version()
 | |
|     if sys_glibc < version:
 | |
|         return False
 | |
|     # Check for presence of _manylinux module.
 | |
|     try:
 | |
|         import _manylinux
 | |
|     except ImportError:
 | |
|         return True
 | |
|     if hasattr(_manylinux, "manylinux_compatible"):
 | |
|         result = _manylinux.manylinux_compatible(version[0], version[1], arch)
 | |
|         if result is not None:
 | |
|             return bool(result)
 | |
|         return True
 | |
|     if version == _GLibCVersion(2, 5):
 | |
|         if hasattr(_manylinux, "manylinux1_compatible"):
 | |
|             return bool(_manylinux.manylinux1_compatible)
 | |
|     if version == _GLibCVersion(2, 12):
 | |
|         if hasattr(_manylinux, "manylinux2010_compatible"):
 | |
|             return bool(_manylinux.manylinux2010_compatible)
 | |
|     if version == _GLibCVersion(2, 17):
 | |
|         if hasattr(_manylinux, "manylinux2014_compatible"):
 | |
|             return bool(_manylinux.manylinux2014_compatible)
 | |
|     return True
 | |
| 
 | |
| 
 | |
| _LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
 | |
|     # CentOS 7 w/ glibc 2.17 (PEP 599)
 | |
|     _GLibCVersion(2, 17): "manylinux2014",
 | |
|     # CentOS 6 w/ glibc 2.12 (PEP 571)
 | |
|     _GLibCVersion(2, 12): "manylinux2010",
 | |
|     # CentOS 5 w/ glibc 2.5 (PEP 513)
 | |
|     _GLibCVersion(2, 5): "manylinux1",
 | |
| }
 | |
| 
 | |
| 
 | |
| def platform_tags(archs: Sequence[str]) -> Iterator[str]:
 | |
|     """Generate manylinux tags compatible to the current platform.
 | |
| 
 | |
|     :param archs: Sequence of compatible architectures.
 | |
|         The first one shall be the closest to the actual architecture and be the part of
 | |
|         platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
 | |
|         The ``linux_`` prefix is assumed as a prerequisite for the current platform to
 | |
|         be manylinux-compatible.
 | |
| 
 | |
|     :returns: An iterator of compatible manylinux tags.
 | |
|     """
 | |
|     if not _have_compatible_abi(sys.executable, archs):
 | |
|         return
 | |
|     # Oldest glibc to be supported regardless of architecture is (2, 17).
 | |
|     too_old_glibc2 = _GLibCVersion(2, 16)
 | |
|     if set(archs) & {"x86_64", "i686"}:
 | |
|         # On x86/i686 also oldest glibc to be supported is (2, 5).
 | |
|         too_old_glibc2 = _GLibCVersion(2, 4)
 | |
|     current_glibc = _GLibCVersion(*_get_glibc_version())
 | |
|     glibc_max_list = [current_glibc]
 | |
|     # We can assume compatibility across glibc major versions.
 | |
|     # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
 | |
|     #
 | |
|     # Build a list of maximum glibc versions so that we can
 | |
|     # output the canonical list of all glibc from current_glibc
 | |
|     # down to too_old_glibc2, including all intermediary versions.
 | |
|     for glibc_major in range(current_glibc.major - 1, 1, -1):
 | |
|         glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
 | |
|         glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
 | |
|     for arch in archs:
 | |
|         for glibc_max in glibc_max_list:
 | |
|             if glibc_max.major == too_old_glibc2.major:
 | |
|                 min_minor = too_old_glibc2.minor
 | |
|             else:
 | |
|                 # For other glibc major versions oldest supported is (x, 0).
 | |
|                 min_minor = -1
 | |
|             for glibc_minor in range(glibc_max.minor, min_minor, -1):
 | |
|                 glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
 | |
|                 tag = "manylinux_{}_{}".format(*glibc_version)
 | |
|                 if _is_compatible(arch, glibc_version):
 | |
|                     yield f"{tag}_{arch}"
 | |
|                 # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
 | |
|                 if glibc_version in _LEGACY_MANYLINUX_MAP:
 | |
|                     legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
 | |
|                     if _is_compatible(arch, glibc_version):
 | |
|                         yield f"{legacy_tag}_{arch}"
 |