|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- # This file is dual licensed under the terms of the Apache License, Version
- # 2.0, and the BSD License. See the LICENSE file in the root of this repository
- # for complete details.
-
- import logging
- import platform
- import sys
- import sysconfig
- from importlib.machinery import EXTENSION_SUFFIXES
- from typing import (
- Dict,
- FrozenSet,
- Iterable,
- Iterator,
- List,
- Optional,
- Sequence,
- Tuple,
- Union,
- cast,
- )
-
- from . import _manylinux, _musllinux
-
- logger = logging.getLogger(__name__)
-
- PythonVersion = Sequence[int]
- MacVersion = Tuple[int, int]
-
- INTERPRETER_SHORT_NAMES: Dict[str, str] = {
- "python": "py", # Generic.
- "cpython": "cp",
- "pypy": "pp",
- "ironpython": "ip",
- "jython": "jy",
- }
-
-
- _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
-
-
- class Tag:
- """
- A representation of the tag triple for a wheel.
-
- Instances are considered immutable and thus are hashable. Equality checking
- is also supported.
- """
-
- __slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
-
- def __init__(self, interpreter: str, abi: str, platform: str) -> None:
- self._interpreter = interpreter.lower()
- self._abi = abi.lower()
- self._platform = platform.lower()
- # The __hash__ of every single element in a Set[Tag] will be evaluated each time
- # that a set calls its `.disjoint()` method, which may be called hundreds of
- # times when scanning a page of links for packages with tags matching that
- # Set[Tag]. Pre-computing the value here produces significant speedups for
- # downstream consumers.
- self._hash = hash((self._interpreter, self._abi, self._platform))
-
- @property
- def interpreter(self) -> str:
- return self._interpreter
-
- @property
- def abi(self) -> str:
- return self._abi
-
- @property
- def platform(self) -> str:
- return self._platform
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, Tag):
- return NotImplemented
-
- return (
- (self._hash == other._hash) # Short-circuit ASAP for perf reasons.
- and (self._platform == other._platform)
- and (self._abi == other._abi)
- and (self._interpreter == other._interpreter)
- )
-
- def __hash__(self) -> int:
- return self._hash
-
- def __str__(self) -> str:
- return f"{self._interpreter}-{self._abi}-{self._platform}"
-
- def __repr__(self) -> str:
- return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
-
-
- def parse_tag(tag: str) -> FrozenSet[Tag]:
- """
- Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
-
- Returning a set is required due to the possibility that the tag is a
- compressed tag set.
- """
- tags = set()
- interpreters, abis, platforms = tag.split("-")
- for interpreter in interpreters.split("."):
- for abi in abis.split("."):
- for platform_ in platforms.split("."):
- tags.add(Tag(interpreter, abi, platform_))
- return frozenset(tags)
-
-
- def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
- value = sysconfig.get_config_var(name)
- if value is None and warn:
- logger.debug(
- "Config variable '%s' is unset, Python ABI tag may be incorrect", name
- )
- return value
-
-
- def _normalize_string(string: str) -> str:
- return string.replace(".", "_").replace("-", "_")
-
-
- def _abi3_applies(python_version: PythonVersion) -> bool:
- """
- Determine if the Python version supports abi3.
-
- PEP 384 was first implemented in Python 3.2.
- """
- return len(python_version) > 1 and tuple(python_version) >= (3, 2)
-
-
- def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
- py_version = tuple(py_version) # To allow for version comparison.
- abis = []
- version = _version_nodot(py_version[:2])
- debug = pymalloc = ucs4 = ""
- with_debug = _get_config_var("Py_DEBUG", warn)
- has_refcount = hasattr(sys, "gettotalrefcount")
- # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
- # extension modules is the best option.
- # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
- has_ext = "_d.pyd" in EXTENSION_SUFFIXES
- if with_debug or (with_debug is None and (has_refcount or has_ext)):
- debug = "d"
- if py_version < (3, 8):
- with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
- if with_pymalloc or with_pymalloc is None:
- pymalloc = "m"
- if py_version < (3, 3):
- unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
- if unicode_size == 4 or (
- unicode_size is None and sys.maxunicode == 0x10FFFF
- ):
- ucs4 = "u"
- elif debug:
- # Debug builds can also load "normal" extension modules.
- # We can also assume no UCS-4 or pymalloc requirement.
- abis.append(f"cp{version}")
- abis.insert(
- 0,
- "cp{version}{debug}{pymalloc}{ucs4}".format(
- version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
- ),
- )
- return abis
-
-
- def cpython_tags(
- python_version: Optional[PythonVersion] = None,
- abis: Optional[Iterable[str]] = None,
- platforms: Optional[Iterable[str]] = None,
- *,
- warn: bool = False,
- ) -> Iterator[Tag]:
- """
- Yields the tags for a CPython interpreter.
-
- The tags consist of:
- - cp<python_version>-<abi>-<platform>
- - cp<python_version>-abi3-<platform>
- - cp<python_version>-none-<platform>
- - cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2.
-
- If python_version only specifies a major version then user-provided ABIs and
- the 'none' ABItag will be used.
-
- If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
- their normal position and not at the beginning.
- """
- if not python_version:
- python_version = sys.version_info[:2]
-
- interpreter = "cp{}".format(_version_nodot(python_version[:2]))
-
- if abis is None:
- if len(python_version) > 1:
- abis = _cpython_abis(python_version, warn)
- else:
- abis = []
- abis = list(abis)
- # 'abi3' and 'none' are explicitly handled later.
- for explicit_abi in ("abi3", "none"):
- try:
- abis.remove(explicit_abi)
- except ValueError:
- pass
-
- platforms = list(platforms or _platform_tags())
- for abi in abis:
- for platform_ in platforms:
- yield Tag(interpreter, abi, platform_)
- if _abi3_applies(python_version):
- yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
- yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
-
- if _abi3_applies(python_version):
- for minor_version in range(python_version[1] - 1, 1, -1):
- for platform_ in platforms:
- interpreter = "cp{version}".format(
- version=_version_nodot((python_version[0], minor_version))
- )
- yield Tag(interpreter, "abi3", platform_)
-
-
- def _generic_abi() -> Iterator[str]:
- abi = sysconfig.get_config_var("SOABI")
- if abi:
- yield _normalize_string(abi)
-
-
- def generic_tags(
- interpreter: Optional[str] = None,
- abis: Optional[Iterable[str]] = None,
- platforms: Optional[Iterable[str]] = None,
- *,
- warn: bool = False,
- ) -> Iterator[Tag]:
- """
- Yields the tags for a generic interpreter.
-
- The tags consist of:
- - <interpreter>-<abi>-<platform>
-
- The "none" ABI will be added if it was not explicitly provided.
- """
- if not interpreter:
- interp_name = interpreter_name()
- interp_version = interpreter_version(warn=warn)
- interpreter = "".join([interp_name, interp_version])
- if abis is None:
- abis = _generic_abi()
- platforms = list(platforms or _platform_tags())
- abis = list(abis)
- if "none" not in abis:
- abis.append("none")
- for abi in abis:
- for platform_ in platforms:
- yield Tag(interpreter, abi, platform_)
-
-
- def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
- """
- Yields Python versions in descending order.
-
- After the latest version, the major-only version will be yielded, and then
- all previous versions of that major version.
- """
- if len(py_version) > 1:
- yield "py{version}".format(version=_version_nodot(py_version[:2]))
- yield "py{major}".format(major=py_version[0])
- if len(py_version) > 1:
- for minor in range(py_version[1] - 1, -1, -1):
- yield "py{version}".format(version=_version_nodot((py_version[0], minor)))
-
-
- def compatible_tags(
- python_version: Optional[PythonVersion] = None,
- interpreter: Optional[str] = None,
- platforms: Optional[Iterable[str]] = None,
- ) -> Iterator[Tag]:
- """
- Yields the sequence of tags that are compatible with a specific version of Python.
-
- The tags consist of:
- - py*-none-<platform>
- - <interpreter>-none-any # ... if `interpreter` is provided.
- - py*-none-any
- """
- if not python_version:
- python_version = sys.version_info[:2]
- platforms = list(platforms or _platform_tags())
- for version in _py_interpreter_range(python_version):
- for platform_ in platforms:
- yield Tag(version, "none", platform_)
- if interpreter:
- yield Tag(interpreter, "none", "any")
- for version in _py_interpreter_range(python_version):
- yield Tag(version, "none", "any")
-
-
- def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
- if not is_32bit:
- return arch
-
- if arch.startswith("ppc"):
- return "ppc"
-
- return "i386"
-
-
- def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
- formats = [cpu_arch]
- if cpu_arch == "x86_64":
- if version < (10, 4):
- return []
- formats.extend(["intel", "fat64", "fat32"])
-
- elif cpu_arch == "i386":
- if version < (10, 4):
- return []
- formats.extend(["intel", "fat32", "fat"])
-
- elif cpu_arch == "ppc64":
- # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
- if version > (10, 5) or version < (10, 4):
- return []
- formats.append("fat64")
-
- elif cpu_arch == "ppc":
- if version > (10, 6):
- return []
- formats.extend(["fat32", "fat"])
-
- if cpu_arch in {"arm64", "x86_64"}:
- formats.append("universal2")
-
- if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
- formats.append("universal")
-
- return formats
-
-
- def mac_platforms(
- version: Optional[MacVersion] = None, arch: Optional[str] = None
- ) -> Iterator[str]:
- """
- Yields the platform tags for a macOS system.
-
- The `version` parameter is a two-item tuple specifying the macOS version to
- generate platform tags for. The `arch` parameter is the CPU architecture to
- generate platform tags for. Both parameters default to the appropriate value
- for the current system.
- """
- version_str, _, cpu_arch = platform.mac_ver()
- if version is None:
- version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
- else:
- version = version
- if arch is None:
- arch = _mac_arch(cpu_arch)
- else:
- arch = arch
-
- if (10, 0) <= version and version < (11, 0):
- # Prior to Mac OS 11, each yearly release of Mac OS bumped the
- # "minor" version number. The major version was always 10.
- for minor_version in range(version[1], -1, -1):
- compat_version = 10, minor_version
- binary_formats = _mac_binary_formats(compat_version, arch)
- for binary_format in binary_formats:
- yield "macosx_{major}_{minor}_{binary_format}".format(
- major=10, minor=minor_version, binary_format=binary_format
- )
-
- if version >= (11, 0):
- # Starting with Mac OS 11, each yearly release bumps the major version
- # number. The minor versions are now the midyear updates.
- for major_version in range(version[0], 10, -1):
- compat_version = major_version, 0
- binary_formats = _mac_binary_formats(compat_version, arch)
- for binary_format in binary_formats:
- yield "macosx_{major}_{minor}_{binary_format}".format(
- major=major_version, minor=0, binary_format=binary_format
- )
-
- if version >= (11, 0):
- # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
- # Arm64 support was introduced in 11.0, so no Arm binaries from previous
- # releases exist.
- #
- # However, the "universal2" binary format can have a
- # macOS version earlier than 11.0 when the x86_64 part of the binary supports
- # that version of macOS.
- if arch == "x86_64":
- for minor_version in range(16, 3, -1):
- compat_version = 10, minor_version
- binary_formats = _mac_binary_formats(compat_version, arch)
- for binary_format in binary_formats:
- yield "macosx_{major}_{minor}_{binary_format}".format(
- major=compat_version[0],
- minor=compat_version[1],
- binary_format=binary_format,
- )
- else:
- for minor_version in range(16, 3, -1):
- compat_version = 10, minor_version
- binary_format = "universal2"
- yield "macosx_{major}_{minor}_{binary_format}".format(
- major=compat_version[0],
- minor=compat_version[1],
- binary_format=binary_format,
- )
-
-
- def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
- linux = _normalize_string(sysconfig.get_platform())
- if is_32bit:
- if linux == "linux_x86_64":
- linux = "linux_i686"
- elif linux == "linux_aarch64":
- linux = "linux_armv7l"
- _, arch = linux.split("_", 1)
- yield from _manylinux.platform_tags(linux, arch)
- yield from _musllinux.platform_tags(arch)
- yield linux
-
-
- def _generic_platforms() -> Iterator[str]:
- yield _normalize_string(sysconfig.get_platform())
-
-
- def _platform_tags() -> Iterator[str]:
- """
- Provides the platform tags for this installation.
- """
- if platform.system() == "Darwin":
- return mac_platforms()
- elif platform.system() == "Linux":
- return _linux_platforms()
- else:
- return _generic_platforms()
-
-
- def interpreter_name() -> str:
- """
- Returns the name of the running interpreter.
- """
- name = sys.implementation.name
- return INTERPRETER_SHORT_NAMES.get(name) or name
-
-
- def interpreter_version(*, warn: bool = False) -> str:
- """
- Returns the version of the running interpreter.
- """
- version = _get_config_var("py_version_nodot", warn=warn)
- if version:
- version = str(version)
- else:
- version = _version_nodot(sys.version_info[:2])
- return version
-
-
- def _version_nodot(version: PythonVersion) -> str:
- return "".join(map(str, version))
-
-
- def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
- """
- Returns the sequence of tag triples for the running interpreter.
-
- The order of the sequence corresponds to priority order for the
- interpreter, from most to least important.
- """
-
- interp_name = interpreter_name()
- if interp_name == "cp":
- yield from cpython_tags(warn=warn)
- else:
- yield from generic_tags()
-
- yield from compatible_tags()
|