| # Copyright 2014 Pants project contributors (see CONTRIBUTORS.md). |
| # Licensed under the Apache License, Version 2.0 (see LICENSE). |
| |
| """PEP425 handling for pex |
| |
| PEP425 (http://legacy.python.org/dev/peps/pep-0425/) describes a tagging system used to determine |
| whether or not a distribution's platform is compatible with the current platform. It is the |
| tagging system used to describe platform compatibility for wheel files. |
| """ |
| |
| from pkg_resources import get_supported_platform |
| |
| from .platforms import Platform |
| |
| |
| class PEP425Extras(object): |
| """Extensions to platform handling beyond PEP425.""" |
| |
| @classmethod |
| def is_macosx_platform(cls, platform): |
| return platform.startswith('macosx') |
| |
| @classmethod |
| def parse_macosx_tag(cls, platform_tag): |
| invalid_tag = ValueError('invalid macosx tag: %s' % platform_tag) |
| if not cls.is_macosx_platform(platform_tag): |
| raise invalid_tag |
| segments = platform_tag.split('_', 3) |
| if len(segments) != 4: |
| raise invalid_tag |
| if segments[0] != 'macosx': |
| raise invalid_tag |
| try: |
| major, minor = int(segments[1]), int(segments[2]) |
| platform = segments[3] |
| except ValueError: |
| raise invalid_tag |
| return major, minor, platform |
| |
| @classmethod |
| def iter_compatible_osx_platforms(cls, supported_platform): |
| platform_major, platform_minor, platform = cls.parse_macosx_tag(supported_platform) |
| platform_equivalents = set(Platform.MACOSX_PLATFORM_COMPATIBILITY.get(platform, ())) |
| platform_equivalents.add(platform) |
| for minor in range(platform_minor, -1, -1): |
| for binary_compat in platform_equivalents: |
| yield 'macosx_%s_%s_%s' % (platform_major, minor, binary_compat) |
| |
| @classmethod |
| def platform_iterator(cls, platform): |
| """Iterate over all compatible platform tags of a supplied platform tag. |
| |
| :param platform: the platform tag to iterate over |
| """ |
| if cls.is_macosx_platform(platform): |
| for plat in cls.iter_compatible_osx_platforms(platform): |
| yield plat |
| else: |
| yield platform |
| |
| |
| class PEP425(object): # noqa |
| INTERPRETER_TAGS = { |
| 'CPython': 'cp', |
| 'Jython': 'jy', |
| 'PyPy': 'pp', |
| 'IronPython': 'ip', |
| } |
| |
| @classmethod |
| def get_implementation_tag(cls, interpreter_subversion): |
| return cls.INTERPRETER_TAGS.get(interpreter_subversion) |
| |
| @classmethod |
| def get_version_tag(cls, interpreter_version): |
| return ''.join(map(str, interpreter_version[:2])) |
| |
| @classmethod |
| def translate_platform_to_tag(cls, platform): |
| return platform.replace('.', '_').replace('-', '_') |
| |
| @classmethod |
| def get_platform_tag(cls): |
| return cls.translate_platform_to_tag(get_supported_platform()) |
| |
| # TODO(wickman) This implementation is technically incorrect but we need to be able to |
| # predict the supported tags of an interpreter that may not be on this machine or |
| # of a different platform. Alternatively we could store the manifest of supported tags |
| # of a targeted platform in a file to be more correct. |
| @classmethod |
| def _iter_supported_tags(cls, impl, version, platform): |
| """Given a set of tags, iterate over supported tags. |
| |
| :param impl: Python implementation tag e.g. cp, jy, pp. |
| :param version: E.g. '27', '33' |
| :param platform: Platform as from :function:`pkg_resources.get_supported_platform`, |
| for example 'linux-x86_64' or 'macosx-10.4-x86_64'. |
| :returns: Iterator over (pyver, abi, platform) tuples. |
| """ |
| # Predict soabi for reasonable interpreters. This is technically wrong but essentially right. |
| abis = [] |
| if impl == 'cp' and (version.startswith('2') or version.startswith('3')): |
| abis.extend([ |
| 'cp%s' % version, |
| 'cp%sdmu' % version, 'cp%sdm' % version, 'cp%sdu' % version, 'cp%sd' % version, |
| 'cp%smu' % version, 'cp%sm' % version, |
| 'cp%su' % version |
| ]) |
| |
| if version.startswith('3'): |
| abis.extend([ |
| 'abi3' |
| ]) |
| |
| major_version = int(version[0]) |
| minor_versions = [] |
| for minor in range(int(version[1]), -1, -1): |
| minor_versions.append('%d%d' % (major_version, minor)) |
| |
| platforms = list(PEP425Extras.platform_iterator(cls.translate_platform_to_tag(platform))) |
| |
| # interpreter specific |
| for p in platforms: |
| for abi in abis: |
| yield ('%s%s' % (impl, version), abi, p) |
| |
| # everything else |
| for p in platforms + ['any']: |
| for i in ('py', impl): |
| yield ('%s%d' % (i, major_version), 'none', p) |
| for minor_version in minor_versions: |
| yield ('%s%s' % (i, minor_version), 'none', p) |
| |
| @classmethod |
| def iter_supported_tags(cls, identity, platform=get_supported_platform()): |
| """Iterate over the supported tag tuples of this interpreter. |
| |
| :param identity: python interpreter identity over which tags should iterate. |
| :type identity: :class:`PythonIdentity` |
| :param platform: python platform over which tags should iterate, by default the current |
| platform. |
| :returns: Iterator over valid PEP425 tag tuples. |
| """ |
| impl_tag = cls.get_implementation_tag(identity.interpreter) |
| vers_tag = cls.get_version_tag(identity.version) |
| tag_iterator = cls._iter_supported_tags(impl_tag, vers_tag, platform) |
| for tag in tag_iterator: |
| yield tag |