File: _tags.py

package info (click to toggle)
meson-python 0.17.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,120 kB
  • sloc: python: 2,788; ansic: 219; makefile: 8
file content (183 lines) | stat: -rw-r--r-- 6,661 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# SPDX-FileCopyrightText: 2022 The meson-python developers
#
# SPDX-License-Identifier: MIT

from __future__ import annotations

import os
import platform
import struct
import sys
import sysconfig
import typing


if typing.TYPE_CHECKING:  # pragma: no cover
    from typing import Optional, Union


# https://peps.python.org/pep-0425/#python-tag
INTERPRETERS = {
    'python': 'py',
    'cpython': 'cp',
    'pypy': 'pp',
    'ironpython': 'ip',
    'jython': 'jy',
}


_32_BIT_INTERPRETER = struct.calcsize('P') == 4


def get_interpreter_tag() -> str:
    name = sys.implementation.name
    name = INTERPRETERS.get(name, name)
    version = sys.version_info
    return f'{name}{version[0]}{version[1]}'


def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]:
    value: Union[str, int, None] = sysconfig.get_config_var(name)
    if value is None:
        return default
    return value


def _get_cpython_abi() -> str:
    version = sys.version_info
    debug = pymalloc = ''
    if _get_config_var('Py_DEBUG', hasattr(sys, 'gettotalrefcount')):
        debug = 'd'
    if version < (3, 8) and _get_config_var('WITH_PYMALLOC', True):
        pymalloc = 'm'
    return f'cp{version[0]}{version[1]}{debug}{pymalloc}'


def get_abi_tag() -> str:
    # The best solution to obtain the Python ABI is to parse the
    # $SOABI or $EXT_SUFFIX sysconfig variables as defined in PEP-314.

    # PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
    # Using $EXT_SUFFIX will not break when PyPy will fix this.
    # See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
    # https://github.com/pypa/packaging/pull/607.
    try:
        empty, abi, ext = str(sysconfig.get_config_var('EXT_SUFFIX')).split('.')
    except ValueError as exc:
        # CPython <= 3.8.7 on Windows does not implement PEP3149 and
        # uses '.pyd' as $EXT_SUFFIX, which does not allow to extract
        # the interpreter ABI.  Check that the fallback is not hit for
        # any other Python implementation.
        if sys.implementation.name != 'cpython':
            raise NotImplementedError from exc
        return _get_cpython_abi()

    # The packaging module initially based his understanding of the
    # $SOABI variable on the inconsistent value reported by PyPy, and
    # did not strip architecture information from it.  Therefore the
    # ABI tag for later Python implementations (all the ones not
    # explicitly handled below) contains architecture information too.
    # Unfortunately, fixing this now would break compatibility.

    if abi.startswith('cpython'):
        abi = 'cp' + abi.split('-')[1]
    elif abi.startswith('cp'):
        abi = abi.split('-')[0]
    elif abi.startswith('pypy'):
        abi = '_'.join(abi.split('-')[:2])
    elif abi.startswith('graalpy'):
        abi = '_'.join(abi.split('-')[:3])

    return abi.replace('.', '_').replace('-', '_')


def _get_macosx_platform_tag() -> str:
    ver, _, arch = platform.mac_ver()

    # Override the architecture with the one provided in the
    # _PYTHON_HOST_PLATFORM environment variable.  This environment
    # variable affects the sysconfig.get_platform() return value and
    # is used to cross-compile python extensions on macOS for a
    # different architecture.  We base the platform tag computation on
    # platform.mac_ver() but respect the content of the environment
    # variable.
    try:
        arch = os.environ.get('_PYTHON_HOST_PLATFORM', '').split('-')[2]
    except IndexError:
        pass

    # Override the macOS version if one is provided via the
    # MACOSX_DEPLOYMENT_TARGET environment variable.
    try:
        version = tuple(map(int, os.environ.get('MACOSX_DEPLOYMENT_TARGET', '').split('.')))[:2]
    except ValueError:
        version = tuple(map(int, ver.split('.')))[:2]

    # Python built with older macOS SDK on macOS 11, reports an
    # unexising macOS 10.16 version instead of the real version.
    #
    # The packaging module introduced a workaround
    # https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
    #
    # This results in packaging versions up to 21.3 generating
    # platform tags like "macosx_10_16_x86_64" and later versions
    # generating "macosx_11_0_x86_64".  Using the latter would be more
    # correct but prevents the resulting wheel from being installed on
    # systems using packaging 21.3 or earlier (pip 22.3 or earlier).
    #
    # Fortunately packaging versions carrying the workaround still
    # accepts "macosx_10_16_x86_64" as a compatible platform tag.  We
    # can therefore ignore the issue and generate the slightly
    # incorrect tag.

    # The minimum macOS ABI version on arm64 is 11.0.  The macOS SDK
    # on arm64 silently bumps any compatibility version specified via
    # the MACOSX_DEPLOYMENT_TARGET environment variable to 11.0.
    # Despite the platform ABI tag being intended to be a minimum
    # compatibility version, pip refuses to install wheels with a
    # platform tag specifying an ABI version lower than 11.0.  Use
    # 11.0 as minimum ABI version on arm64.
    if arch == 'arm64' and version < (11, 0):
        version = (11, 0)

    major, minor = version

    if major >= 11:
        # For macOS reelases up to 10.15, the major version number is
        # actually part of the OS name and the minor version is the
        # actual OS release.  Starting with macOS 11, the major
        # version number is the OS release and the minor version is
        # the patch level.  Reset the patch level to zero.
        minor = 0

    if _32_BIT_INTERPRETER:
        # 32-bit Python running on a 64-bit kernel.
        if arch == 'ppc64':
            arch = 'ppc'
        if arch == 'x86_64':
            arch = 'i386'

    return f'macosx_{major}_{minor}_{arch}'


def get_platform_tag() -> str:
    platform = sysconfig.get_platform()
    if platform.startswith('macosx'):
        return _get_macosx_platform_tag()
    if _32_BIT_INTERPRETER:
        # 32-bit Python running on a 64-bit kernel.
        if platform == 'linux-x86_64':
            return 'linux_i686'
        if platform == 'linux-aarch64':
            return 'linux_armv7l'
    return platform.replace('-', '_').replace('.', '_').lower()


class Tag:
    def __init__(self, interpreter: Optional[str] = None, abi: Optional[str] = None, platform: Optional[str] = None):
        self.interpreter = interpreter or get_interpreter_tag()
        self.abi = abi or get_abi_tag()
        self.platform = platform or get_platform_tag()

    def __str__(self) -> str:
        return f'{self.interpreter}-{self.abi}-{self.platform}'