You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
13 KiB
Python
405 lines
13 KiB
Python
# 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-<platform>
|
|
- <interpreter>-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<major>[0-9]+)\.(?P<minor>[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
|