# 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. from __future__ import absolute_import import distutils.util try: from importlib.machinery import EXTENSION_SUFFIXES except ImportError: # pragma: no cover import imp EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] del imp import platform import re import sys import sysconfig import warnings INTERPRETER_SHORT_NAMES = { "python": "py", # Generic. "cpython": "cp", "pypy": "pp", "ironpython": "ip", "jython": "jy", } _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 class Tag(object): __slots__ = ["_interpreter", "_abi", "_platform"] def __init__(self, interpreter, abi, platform): self._interpreter = interpreter.lower() self._abi = abi.lower() self._platform = platform.lower() @property def interpreter(self): return self._interpreter @property def abi(self): return self._abi @property def platform(self): return self._platform def __eq__(self, other): return ( (self.platform == other.platform) and (self.abi == other.abi) and (self.interpreter == other.interpreter) ) def __hash__(self): return hash((self._interpreter, self._abi, self._platform)) def __str__(self): return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) def __repr__(self): return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) def parse_tag(tag): 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 _normalize_string(string): return string.replace(".", "_").replace("-", "_") def _cpython_interpreter(py_version): # TODO: Is using py_version_nodot for interpreter version critical? return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) def _cpython_abis(py_version): abis = [] version = "{}{}".format(*py_version[:2]) debug = pymalloc = ucs4 = "" with_debug = sysconfig.get_config_var("Py_DEBUG") 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 = sysconfig.get_config_var("WITH_PYMALLOC") if with_pymalloc or with_pymalloc is None: pymalloc = "m" if py_version < (3, 3): unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE") 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("cp{version}".format(version=version)) abis.insert( 0, "cp{version}{debug}{pymalloc}{ucs4}".format( version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 ), ) return abis def _cpython_tags(py_version, interpreter, abis, platforms): for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): yield tag for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): yield tag # PEP 384 was first implemented in Python 3.2. for minor_version in range(py_version[1] - 1, 1, -1): for platform_ in platforms: interpreter = "cp{major}{minor}".format( major=py_version[0], minor=minor_version ) yield Tag(interpreter, "abi3", platform_) def _pypy_interpreter(): return "pp{py_major}{pypy_major}{pypy_minor}".format( py_major=sys.version_info[0], pypy_major=sys.pypy_version_info.major, pypy_minor=sys.pypy_version_info.minor, ) def _generic_abi(): abi = sysconfig.get_config_var("SOABI") if abi: return _normalize_string(abi) else: return "none" def _pypy_tags(py_version, interpreter, abi, platforms): for tag in (Tag(interpreter, abi, platform) for platform in platforms): yield tag for tag in (Tag(interpreter, "none", platform) for platform in platforms): yield tag def _generic_tags(interpreter, py_version, abi, platforms): for tag in (Tag(interpreter, abi, platform) for platform in platforms): yield tag if abi != "none": tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) for tag in tags: yield tag def _py_interpreter_range(py_version): """ Yield Python versions in descending order. After the latest version, the major-only version will be yielded, and then all following versions up to 'end'. """ yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) yield "py{major}".format(major=py_version[0]) for minor in range(py_version[1] - 1, -1, -1): yield "py{major}{minor}".format(major=py_version[0], minor=minor) def _independent_tags(interpreter, py_version, platforms): """ Return the sequence of tags that are consistent across implementations. The tags consist of: - py*-none- - -none-any - py*-none-any """ for version in _py_interpreter_range(py_version): for platform_ in platforms: yield Tag(version, "none", platform_) yield Tag(interpreter, "none", "any") for version in _py_interpreter_range(py_version): yield Tag(version, "none", "any") def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): if not is_32bit: return arch if arch.startswith("ppc"): return "ppc" return "i386" def _mac_binary_formats(version, cpu_arch): 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"]) formats.append("universal") return formats def _mac_platforms(version=None, arch=None): version_str, _, cpu_arch = platform.mac_ver() if version is None: version = tuple(map(int, version_str.split(".")[:2])) if arch is None: arch = _mac_arch(cpu_arch) platforms = [] for minor_version in range(version[1], -1, -1): compat_version = version[0], minor_version binary_formats = _mac_binary_formats(compat_version, arch) for binary_format in binary_formats: platforms.append( "macosx_{major}_{minor}_{binary_format}".format( major=compat_version[0], minor=compat_version[1], binary_format=binary_format, ) ) return platforms # From PEP 513. def _is_manylinux_compatible(name, glibc_version): # Check for presence of _manylinux module. try: import _manylinux return bool(getattr(_manylinux, name + "_compatible")) except (ImportError, AttributeError): # Fall through to heuristic check below. pass return _have_compatible_glibc(*glibc_version) def _glibc_version_string(): # Returns glibc version string, or None if not using glibc. import ctypes # 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. process_namespace = ctypes.CDLL(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 = gnu_get_libc_version() # py2 / py3 compatibility: if not isinstance(version_str, str): version_str = version_str.decode("ascii") return version_str # Separated out from have_compatible_glibc for easier unit testing. def _check_glibc_version(version_str, required_major, minimum_minor): # Parse string and check against requested 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[0-9]+)\.(?P[0-9]+)", version_str) if not m: warnings.warn( "Expected glibc version with 2 components major.minor," " got: %s" % version_str, RuntimeWarning, ) return False return ( int(m.group("major")) == required_major and int(m.group("minor")) >= minimum_minor ) def _have_compatible_glibc(required_major, minimum_minor): version_str = _glibc_version_string() if version_str is None: return False return _check_glibc_version(version_str, required_major, minimum_minor) def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): linux = _normalize_string(distutils.util.get_platform()) if linux == "linux_x86_64" and is_32bit: linux = "linux_i686" manylinux_support = ( ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599) ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571) ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513) ) manylinux_support_iter = iter(manylinux_support) for name, glibc_version in manylinux_support_iter: if _is_manylinux_compatible(name, glibc_version): platforms = [linux.replace("linux", name)] break else: platforms = [] # Support for a later manylinux implies support for an earlier version. platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] platforms.append(linux) return platforms def _generic_platforms(): platform = _normalize_string(distutils.util.get_platform()) return [platform] def _interpreter_name(): name = platform.python_implementation().lower() return INTERPRETER_SHORT_NAMES.get(name) or name def _generic_interpreter(name, py_version): version = sysconfig.get_config_var("py_version_nodot") if not version: version = "".join(map(str, py_version[:2])) return "{name}{version}".format(name=name, version=version) def sys_tags(): """ 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. """ py_version = sys.version_info[:2] interpreter_name = _interpreter_name() if platform.system() == "Darwin": platforms = _mac_platforms() elif platform.system() == "Linux": platforms = _linux_platforms() else: platforms = _generic_platforms() if interpreter_name == "cp": interpreter = _cpython_interpreter(py_version) abis = _cpython_abis(py_version) for tag in _cpython_tags(py_version, interpreter, abis, platforms): yield tag elif interpreter_name == "pp": interpreter = _pypy_interpreter() abi = _generic_abi() for tag in _pypy_tags(py_version, interpreter, abi, platforms): yield tag else: interpreter = _generic_interpreter(interpreter_name, py_version) abi = _generic_abi() for tag in _generic_tags(interpreter, py_version, abi, platforms): yield tag for tag in _independent_tags(interpreter, py_version, platforms): yield tag