text: # 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
  