1# This file originally from pip:
2# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py
3"""Generate and work with PEP 425 Compatibility Tags."""
4from __future__ import absolute_import
5
6import distutils.util
7from distutils import log
8import platform
9import re
10import sys
11import sysconfig
12import warnings
13from collections import OrderedDict
14
15from . import glibc
16
17_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')
18
19
20def get_config_var(var):
21    try:
22        return sysconfig.get_config_var(var)
23    except IOError as e:  # Issue #1074
24        warnings.warn("{}".format(e), RuntimeWarning)
25        return None
26
27
28def get_abbr_impl():
29    """Return abbreviated implementation name."""
30    if hasattr(sys, 'pypy_version_info'):
31        pyimpl = 'pp'
32    elif sys.platform.startswith('java'):
33        pyimpl = 'jy'
34    elif sys.platform == 'cli':
35        pyimpl = 'ip'
36    else:
37        pyimpl = 'cp'
38    return pyimpl
39
40
41def get_impl_ver():
42    """Return implementation version."""
43    impl_ver = get_config_var("py_version_nodot")
44    if not impl_ver or get_abbr_impl() == 'pp':
45        impl_ver = ''.join(map(str, get_impl_version_info()))
46    return impl_ver
47
48
49def get_impl_version_info():
50    """Return sys.version_info-like tuple for use in decrementing the minor
51    version."""
52    if get_abbr_impl() == 'pp':
53        # as per https://github.com/pypa/pip/issues/2882
54        return (sys.version_info[0], sys.pypy_version_info.major,
55                sys.pypy_version_info.minor)
56    else:
57        return sys.version_info[0], sys.version_info[1]
58
59
60def get_impl_tag():
61    """
62    Returns the Tag for this specific implementation.
63    """
64    return "{}{}".format(get_abbr_impl(), get_impl_ver())
65
66
67def get_flag(var, fallback, expected=True, warn=True):
68    """Use a fallback method for determining SOABI flags if the needed config
69    var is unset or unavailable."""
70    val = get_config_var(var)
71    if val is None:
72        if warn:
73            log.debug("Config variable '%s' is unset, Python ABI tag may "
74                      "be incorrect", var)
75        return fallback()
76    return val == expected
77
78
79def get_abi_tag():
80    """Return the ABI tag based on SOABI (if available) or emulate SOABI
81    (CPython 2, PyPy)."""
82    soabi = get_config_var('SOABI')
83    impl = get_abbr_impl()
84    if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
85        d = ''
86        m = ''
87        u = ''
88        if get_flag('Py_DEBUG',
89                    lambda: hasattr(sys, 'gettotalrefcount'),
90                    warn=(impl == 'cp')):
91            d = 'd'
92        if get_flag('WITH_PYMALLOC',
93                    lambda: impl == 'cp',
94                    warn=(impl == 'cp')):
95            m = 'm'
96        if get_flag('Py_UNICODE_SIZE',
97                    lambda: sys.maxunicode == 0x10ffff,
98                    expected=4,
99                    warn=(impl == 'cp' and
100                          sys.version_info < (3, 3))) \
101                and sys.version_info < (3, 3):
102            u = 'u'
103        abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u)
104    elif soabi and soabi.startswith('cpython-'):
105        abi = 'cp' + soabi.split('-')[1]
106    elif soabi:
107        abi = soabi.replace('.', '_').replace('-', '_')
108    else:
109        abi = None
110    return abi
111
112
113def _is_running_32bit():
114    return sys.maxsize == 2147483647
115
116
117def get_platform():
118    """Return our platform name 'win32', 'linux_x86_64'"""
119    if sys.platform == 'darwin':
120        # distutils.util.get_platform() returns the release based on the value
121        # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may
122        # be significantly older than the user's current machine.
123        release, _, machine = platform.mac_ver()
124        split_ver = release.split('.')
125
126        if machine == "x86_64" and _is_running_32bit():
127            machine = "i386"
128        elif machine == "ppc64" and _is_running_32bit():
129            machine = "ppc"
130
131        return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)
132
133    # XXX remove distutils dependency
134    result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
135    if result == "linux_x86_64" and _is_running_32bit():
136        # 32 bit Python program (running on a 64 bit Linux): pip should only
137        # install and run 32 bit compiled extensions in that case.
138        result = "linux_i686"
139
140    return result
141
142
143def is_manylinux1_compatible():
144    # Only Linux, and only x86-64 / i686
145    if get_platform() not in {"linux_x86_64", "linux_i686"}:
146        return False
147
148    # Check for presence of _manylinux module
149    try:
150        import _manylinux
151        return bool(_manylinux.manylinux1_compatible)
152    except (ImportError, AttributeError):
153        # Fall through to heuristic check below
154        pass
155
156    # Check glibc version. CentOS 5 uses glibc 2.5.
157    return glibc.have_compatible_glibc(2, 5)
158
159
160def get_darwin_arches(major, minor, machine):
161    """Return a list of supported arches (including group arches) for
162    the given major, minor and machine architecture of an macOS machine.
163    """
164    arches = []
165
166    def _supports_arch(major, minor, arch):
167        # Looking at the application support for macOS versions in the chart
168        # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears
169        # our timeline looks roughly like:
170        #
171        # 10.0 - Introduces ppc support.
172        # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64
173        #        and x86_64 support is CLI only, and cannot be used for GUI
174        #        applications.
175        # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications.
176        # 10.6 - Drops support for ppc64
177        # 10.7 - Drops support for ppc
178        #
179        # Given that we do not know if we're installing a CLI or a GUI
180        # application, we must be conservative and assume it might be a GUI
181        # application and behave as if ppc64 and x86_64 support did not occur
182        # until 10.5.
183        #
184        # Note: The above information is taken from the "Application support"
185        #       column in the chart not the "Processor support" since I believe
186        #       that we care about what instruction sets an application can use
187        #       not which processors the OS supports.
188        if arch == 'ppc':
189            return (major, minor) <= (10, 5)
190        if arch == 'ppc64':
191            return (major, minor) == (10, 5)
192        if arch == 'i386':
193            return (major, minor) >= (10, 4)
194        if arch == 'x86_64':
195            return (major, minor) >= (10, 5)
196        if arch in groups:
197            for garch in groups[arch]:
198                if _supports_arch(major, minor, garch):
199                    return True
200        return False
201
202    groups = OrderedDict([
203        ("fat", ("i386", "ppc")),
204        ("intel", ("x86_64", "i386")),
205        ("fat64", ("x86_64", "ppc64")),
206        ("fat32", ("x86_64", "i386", "ppc")),
207    ])
208
209    if _supports_arch(major, minor, machine):
210        arches.append(machine)
211
212    for garch in groups:
213        if machine in groups[garch] and _supports_arch(major, minor, garch):
214            arches.append(garch)
215
216    arches.append('universal')
217
218    return arches
219
220
221def get_supported(versions=None, noarch=False, platform=None,
222                  impl=None, abi=None):
223    """Return a list of supported tags for each version specified in
224    `versions`.
225
226    :param versions: a list of string versions, of the form ["33", "32"],
227        or None. The first version will be assumed to support our ABI.
228    :param platform: specify the exact platform you want valid
229        tags for, or None. If None, use the local system platform.
230    :param impl: specify the exact implementation you want valid
231        tags for, or None. If None, use the local interpreter impl.
232    :param abi: specify the exact abi you want valid
233        tags for, or None. If None, use the local interpreter abi.
234    """
235    supported = []
236
237    # Versions must be given with respect to the preference
238    if versions is None:
239        versions = []
240        version_info = get_impl_version_info()
241        major = version_info[:-1]
242        # Support all previous minor Python versions.
243        for minor in range(version_info[-1], -1, -1):
244            versions.append(''.join(map(str, major + (minor,))))
245
246    impl = impl or get_abbr_impl()
247
248    abis = []
249
250    abi = abi or get_abi_tag()
251    if abi:
252        abis[0:0] = [abi]
253
254    abi3s = set()
255    import imp
256    for suffix in imp.get_suffixes():
257        if suffix[0].startswith('.abi'):
258            abi3s.add(suffix[0].split('.', 2)[1])
259
260    abis.extend(sorted(list(abi3s)))
261
262    abis.append('none')
263
264    if not noarch:
265        arch = platform or get_platform()
266        if arch.startswith('macosx'):
267            # support macosx-10.6-intel on macosx-10.9-x86_64
268            match = _osx_arch_pat.match(arch)
269            if match:
270                name, major, minor, actual_arch = match.groups()
271                tpl = '{}_{}_%i_%s'.format(name, major)
272                arches = []
273                for m in reversed(range(int(minor) + 1)):
274                    for a in get_darwin_arches(int(major), m, actual_arch):
275                        arches.append(tpl % (m, a))
276            else:
277                # arch pattern didn't match (?!)
278                arches = [arch]
279        elif platform is None and is_manylinux1_compatible():
280            arches = [arch.replace('linux', 'manylinux1'), arch]
281        else:
282            arches = [arch]
283
284        # Current version, current API (built specifically for our Python):
285        for abi in abis:
286            for arch in arches:
287                supported.append(('%s%s' % (impl, versions[0]), abi, arch))
288
289        # abi3 modules compatible with older version of Python
290        for version in versions[1:]:
291            # abi3 was introduced in Python 3.2
292            if version in {'31', '30'}:
293                break
294            for abi in abi3s:   # empty set if not Python 3
295                for arch in arches:
296                    supported.append(("%s%s" % (impl, version), abi, arch))
297
298        # Has binaries, does not use the Python API:
299        for arch in arches:
300            supported.append(('py%s' % (versions[0][0]), 'none', arch))
301
302    # No abi / arch, but requires our implementation:
303    supported.append(('%s%s' % (impl, versions[0]), 'none', 'any'))
304    # Tagged specifically as being cross-version compatible
305    # (with just the major version specified)
306    supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any'))
307
308    # No abi / arch, generic Python
309    for i, version in enumerate(versions):
310        supported.append(('py%s' % (version,), 'none', 'any'))
311        if i == 0:
312            supported.append(('py%s' % (version[0]), 'none', 'any'))
313
314    return supported
315
316
317implementation_tag = get_impl_tag()
318