tags.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import absolute_import
  5. import distutils.util
  6. try:
  7. from importlib.machinery import EXTENSION_SUFFIXES
  8. except ImportError: # pragma: no cover
  9. import imp
  10. EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
  11. del imp
  12. import platform
  13. import re
  14. import sys
  15. import sysconfig
  16. import warnings
  17. INTERPRETER_SHORT_NAMES = {
  18. "python": "py", # Generic.
  19. "cpython": "cp",
  20. "pypy": "pp",
  21. "ironpython": "ip",
  22. "jython": "jy",
  23. }
  24. _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
  25. class Tag(object):
  26. __slots__ = ["_interpreter", "_abi", "_platform"]
  27. def __init__(self, interpreter, abi, platform):
  28. self._interpreter = interpreter.lower()
  29. self._abi = abi.lower()
  30. self._platform = platform.lower()
  31. @property
  32. def interpreter(self):
  33. return self._interpreter
  34. @property
  35. def abi(self):
  36. return self._abi
  37. @property
  38. def platform(self):
  39. return self._platform
  40. def __eq__(self, other):
  41. return (
  42. (self.platform == other.platform)
  43. and (self.abi == other.abi)
  44. and (self.interpreter == other.interpreter)
  45. )
  46. def __hash__(self):
  47. return hash((self._interpreter, self._abi, self._platform))
  48. def __str__(self):
  49. return "{}-{}-{}".format(self._interpreter, self._abi, self._platform)
  50. def __repr__(self):
  51. return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
  52. def parse_tag(tag):
  53. tags = set()
  54. interpreters, abis, platforms = tag.split("-")
  55. for interpreter in interpreters.split("."):
  56. for abi in abis.split("."):
  57. for platform_ in platforms.split("."):
  58. tags.add(Tag(interpreter, abi, platform_))
  59. return frozenset(tags)
  60. def _normalize_string(string):
  61. return string.replace(".", "_").replace("-", "_")
  62. def _cpython_interpreter(py_version):
  63. # TODO: Is using py_version_nodot for interpreter version critical?
  64. return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1])
  65. def _cpython_abis(py_version):
  66. abis = []
  67. version = "{}{}".format(*py_version[:2])
  68. debug = pymalloc = ucs4 = ""
  69. with_debug = sysconfig.get_config_var("Py_DEBUG")
  70. has_refcount = hasattr(sys, "gettotalrefcount")
  71. # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
  72. # extension modules is the best option.
  73. # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
  74. has_ext = "_d.pyd" in EXTENSION_SUFFIXES
  75. if with_debug or (with_debug is None and (has_refcount or has_ext)):
  76. debug = "d"
  77. if py_version < (3, 8):
  78. with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC")
  79. if with_pymalloc or with_pymalloc is None:
  80. pymalloc = "m"
  81. if py_version < (3, 3):
  82. unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE")
  83. if unicode_size == 4 or (
  84. unicode_size is None and sys.maxunicode == 0x10FFFF
  85. ):
  86. ucs4 = "u"
  87. elif debug:
  88. # Debug builds can also load "normal" extension modules.
  89. # We can also assume no UCS-4 or pymalloc requirement.
  90. abis.append("cp{version}".format(version=version))
  91. abis.insert(
  92. 0,
  93. "cp{version}{debug}{pymalloc}{ucs4}".format(
  94. version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
  95. ),
  96. )
  97. return abis
  98. def _cpython_tags(py_version, interpreter, abis, platforms):
  99. for abi in abis:
  100. for platform_ in platforms:
  101. yield Tag(interpreter, abi, platform_)
  102. for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms):
  103. yield tag
  104. for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms):
  105. yield tag
  106. # PEP 384 was first implemented in Python 3.2.
  107. for minor_version in range(py_version[1] - 1, 1, -1):
  108. for platform_ in platforms:
  109. interpreter = "cp{major}{minor}".format(
  110. major=py_version[0], minor=minor_version
  111. )
  112. yield Tag(interpreter, "abi3", platform_)
  113. def _pypy_interpreter():
  114. return "pp{py_major}{pypy_major}{pypy_minor}".format(
  115. py_major=sys.version_info[0],
  116. pypy_major=sys.pypy_version_info.major,
  117. pypy_minor=sys.pypy_version_info.minor,
  118. )
  119. def _generic_abi():
  120. abi = sysconfig.get_config_var("SOABI")
  121. if abi:
  122. return _normalize_string(abi)
  123. else:
  124. return "none"
  125. def _pypy_tags(py_version, interpreter, abi, platforms):
  126. for tag in (Tag(interpreter, abi, platform) for platform in platforms):
  127. yield tag
  128. for tag in (Tag(interpreter, "none", platform) for platform in platforms):
  129. yield tag
  130. def _generic_tags(interpreter, py_version, abi, platforms):
  131. for tag in (Tag(interpreter, abi, platform) for platform in platforms):
  132. yield tag
  133. if abi != "none":
  134. tags = (Tag(interpreter, "none", platform_) for platform_ in platforms)
  135. for tag in tags:
  136. yield tag
  137. def _py_interpreter_range(py_version):
  138. """
  139. Yield Python versions in descending order.
  140. After the latest version, the major-only version will be yielded, and then
  141. all following versions up to 'end'.
  142. """
  143. yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1])
  144. yield "py{major}".format(major=py_version[0])
  145. for minor in range(py_version[1] - 1, -1, -1):
  146. yield "py{major}{minor}".format(major=py_version[0], minor=minor)
  147. def _independent_tags(interpreter, py_version, platforms):
  148. """
  149. Return the sequence of tags that are consistent across implementations.
  150. The tags consist of:
  151. - py*-none-<platform>
  152. - <interpreter>-none-any
  153. - py*-none-any
  154. """
  155. for version in _py_interpreter_range(py_version):
  156. for platform_ in platforms:
  157. yield Tag(version, "none", platform_)
  158. yield Tag(interpreter, "none", "any")
  159. for version in _py_interpreter_range(py_version):
  160. yield Tag(version, "none", "any")
  161. def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
  162. if not is_32bit:
  163. return arch
  164. if arch.startswith("ppc"):
  165. return "ppc"
  166. return "i386"
  167. def _mac_binary_formats(version, cpu_arch):
  168. formats = [cpu_arch]
  169. if cpu_arch == "x86_64":
  170. if version < (10, 4):
  171. return []
  172. formats.extend(["intel", "fat64", "fat32"])
  173. elif cpu_arch == "i386":
  174. if version < (10, 4):
  175. return []
  176. formats.extend(["intel", "fat32", "fat"])
  177. elif cpu_arch == "ppc64":
  178. # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
  179. if version > (10, 5) or version < (10, 4):
  180. return []
  181. formats.append("fat64")
  182. elif cpu_arch == "ppc":
  183. if version > (10, 6):
  184. return []
  185. formats.extend(["fat32", "fat"])
  186. formats.append("universal")
  187. return formats
  188. def _mac_platforms(version=None, arch=None):
  189. version_str, _, cpu_arch = platform.mac_ver()
  190. if version is None:
  191. version = tuple(map(int, version_str.split(".")[:2]))
  192. if arch is None:
  193. arch = _mac_arch(cpu_arch)
  194. platforms = []
  195. for minor_version in range(version[1], -1, -1):
  196. compat_version = version[0], minor_version
  197. binary_formats = _mac_binary_formats(compat_version, arch)
  198. for binary_format in binary_formats:
  199. platforms.append(
  200. "macosx_{major}_{minor}_{binary_format}".format(
  201. major=compat_version[0],
  202. minor=compat_version[1],
  203. binary_format=binary_format,
  204. )
  205. )
  206. return platforms
  207. # From PEP 513.
  208. def _is_manylinux_compatible(name, glibc_version):
  209. # Check for presence of _manylinux module.
  210. try:
  211. import _manylinux
  212. return bool(getattr(_manylinux, name + "_compatible"))
  213. except (ImportError, AttributeError):
  214. # Fall through to heuristic check below.
  215. pass
  216. return _have_compatible_glibc(*glibc_version)
  217. def _glibc_version_string():
  218. # Returns glibc version string, or None if not using glibc.
  219. import ctypes
  220. # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
  221. # manpage says, "If filename is NULL, then the returned handle is for the
  222. # main program". This way we can let the linker do the work to figure out
  223. # which libc our process is actually using.
  224. process_namespace = ctypes.CDLL(None)
  225. try:
  226. gnu_get_libc_version = process_namespace.gnu_get_libc_version
  227. except AttributeError:
  228. # Symbol doesn't exist -> therefore, we are not linked to
  229. # glibc.
  230. return None
  231. # Call gnu_get_libc_version, which returns a string like "2.5"
  232. gnu_get_libc_version.restype = ctypes.c_char_p
  233. version_str = gnu_get_libc_version()
  234. # py2 / py3 compatibility:
  235. if not isinstance(version_str, str):
  236. version_str = version_str.decode("ascii")
  237. return version_str
  238. # Separated out from have_compatible_glibc for easier unit testing.
  239. def _check_glibc_version(version_str, required_major, minimum_minor):
  240. # Parse string and check against requested version.
  241. #
  242. # We use a regexp instead of str.split because we want to discard any
  243. # random junk that might come after the minor version -- this might happen
  244. # in patched/forked versions of glibc (e.g. Linaro's version of glibc
  245. # uses version strings like "2.20-2014.11"). See gh-3588.
  246. m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
  247. if not m:
  248. warnings.warn(
  249. "Expected glibc version with 2 components major.minor,"
  250. " got: %s" % version_str,
  251. RuntimeWarning,
  252. )
  253. return False
  254. return (
  255. int(m.group("major")) == required_major
  256. and int(m.group("minor")) >= minimum_minor
  257. )
  258. def _have_compatible_glibc(required_major, minimum_minor):
  259. version_str = _glibc_version_string()
  260. if version_str is None:
  261. return False
  262. return _check_glibc_version(version_str, required_major, minimum_minor)
  263. def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
  264. linux = _normalize_string(distutils.util.get_platform())
  265. if linux == "linux_x86_64" and is_32bit:
  266. linux = "linux_i686"
  267. manylinux_support = (
  268. ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599)
  269. ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571)
  270. ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513)
  271. )
  272. manylinux_support_iter = iter(manylinux_support)
  273. for name, glibc_version in manylinux_support_iter:
  274. if _is_manylinux_compatible(name, glibc_version):
  275. platforms = [linux.replace("linux", name)]
  276. break
  277. else:
  278. platforms = []
  279. # Support for a later manylinux implies support for an earlier version.
  280. platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter]
  281. platforms.append(linux)
  282. return platforms
  283. def _generic_platforms():
  284. platform = _normalize_string(distutils.util.get_platform())
  285. return [platform]
  286. def _interpreter_name():
  287. name = platform.python_implementation().lower()
  288. return INTERPRETER_SHORT_NAMES.get(name) or name
  289. def _generic_interpreter(name, py_version):
  290. version = sysconfig.get_config_var("py_version_nodot")
  291. if not version:
  292. version = "".join(map(str, py_version[:2]))
  293. return "{name}{version}".format(name=name, version=version)
  294. def sys_tags():
  295. """
  296. Returns the sequence of tag triples for the running interpreter.
  297. The order of the sequence corresponds to priority order for the
  298. interpreter, from most to least important.
  299. """
  300. py_version = sys.version_info[:2]
  301. interpreter_name = _interpreter_name()
  302. if platform.system() == "Darwin":
  303. platforms = _mac_platforms()
  304. elif platform.system() == "Linux":
  305. platforms = _linux_platforms()
  306. else:
  307. platforms = _generic_platforms()
  308. if interpreter_name == "cp":
  309. interpreter = _cpython_interpreter(py_version)
  310. abis = _cpython_abis(py_version)
  311. for tag in _cpython_tags(py_version, interpreter, abis, platforms):
  312. yield tag
  313. elif interpreter_name == "pp":
  314. interpreter = _pypy_interpreter()
  315. abi = _generic_abi()
  316. for tag in _pypy_tags(py_version, interpreter, abi, platforms):
  317. yield tag
  318. else:
  319. interpreter = _generic_interpreter(interpreter_name, py_version)
  320. abi = _generic_abi()
  321. for tag in _generic_tags(interpreter, py_version, abi, platforms):
  322. yield tag
  323. for tag in _independent_tags(interpreter, py_version, platforms):
  324. yield tag