Skip to content

Commit 5fbd7d0

Browse files
committed
ENH: do not rely on packaging module for generating wheel tags
The generated tags are tested checking against what the packaging module generates. This should ensure that the two implementations will not diverge.
1 parent e1995df commit 5fbd7d0

File tree

6 files changed

+173
-44
lines changed

6 files changed

+173
-44
lines changed

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ py.install_sources(
1111
'mesonpy/__init__.py',
1212
'mesonpy/_compat.py',
1313
'mesonpy/_elf.py',
14+
'mesonpy/_tags.py',
1415
'mesonpy/_util.py',
1516
subdir: 'mesonpy',
1617
)

mesonpy/__init__.py

+5-39
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636
Union
3737
)
3838

39-
import packaging.tags
40-
4139

4240
if sys.version_info < (3, 11):
4341
import tomli as tomllib
@@ -46,6 +44,7 @@
4644

4745
import mesonpy._compat
4846
import mesonpy._elf
47+
import mesonpy._tags
4948
import mesonpy._util
5049

5150
from mesonpy._compat import Iterator, Path
@@ -109,30 +108,6 @@ def _init_colors() -> Dict[str, str]:
109108
assert all(re.match(_EXTENSION_SUFFIX_REGEX, x) for x in _EXTENSION_SUFFIXES)
110109

111110

112-
def _adjust_manylinux_tag(platform: str) -> str:
113-
# The packaging module generates overly specific platforms tags on
114-
# Linux. The platforms tags on Linux evolved over time. Relax
115-
# the platform tags to maintain compatibility with old wheel
116-
# installation tools. The relaxed platform tags match the ones
117-
# generated by the wheel package.
118-
# https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/
119-
return re.sub(r'^manylinux(1|2010|2014|_\d+_\d+)_(.*)$', r'linux_\2', platform)
120-
121-
122-
def _adjust_darwin_tag(platform: str) -> str:
123-
# Override the macOS version if one is provided via the
124-
# MACOS_DEPLOYMENT_TARGET environment variable. Return it
125-
# unchanged otherwise.
126-
try:
127-
version = tuple(map(int, os.environ.get('MACOS_DEPLOYMENT_TARGET', '').split('.')))[:2]
128-
except ValueError:
129-
version = None
130-
if version is not None:
131-
# str() to silence mypy
132-
platform = str(next(packaging.tags.mac_platforms(version)))
133-
return platform
134-
135-
136111
def _showwarning(
137112
message: Union[Warning, str],
138113
category: Type[Warning],
@@ -219,26 +194,17 @@ def basename(self) -> str:
219194
)
220195

221196
@property
222-
def tag(self) -> packaging.tags.Tag:
197+
def tag(self) -> mesonpy._tags.Tag:
223198
"""Wheel tags."""
224199
if self.is_pure:
225-
return packaging.tags.Tag('py3', 'none', 'any')
226-
# Get the most specific tag for the Python interpreter.
227-
tag = next(packaging.tags.sys_tags())
228-
if tag.platform.startswith('manylinux'):
229-
tag = packaging.tags.Tag(tag.interpreter, tag.abi, _adjust_manylinux_tag(tag.platform))
230-
elif tag.platform.startswith('darwin'):
231-
tag = packaging.tags.Tag(tag.interpreter, tag.abi, _adjust_darwin_tag(tag.platform))
200+
return mesonpy._tags.Tag('py3', 'none', 'any')
232201
if not self._has_extension_modules:
233202
# The wheel has platform dependent code (is not pure) but
234203
# does not contain any extension module (does not
235204
# distribute any file in {platlib}) thus use generic
236205
# implementation and ABI tags.
237-
return packaging.tags.Tag('py3', 'none', tag.platform)
238-
if self._stable_abi:
239-
# All distributed extension modules use the stable ABI.
240-
return packaging.tags.Tag(tag.interpreter, self._stable_abi, tag.platform)
241-
return tag
206+
return mesonpy._tags.Tag('py3', 'none', None)
207+
return mesonpy._tags.Tag(None, self._stable_abi, None)
242208

243209
@property
244210
def name(self) -> str:

mesonpy/_tags.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
import os
4+
import platform
5+
import subprocess
6+
import sys
7+
import sysconfig
8+
9+
from dataclasses import dataclass
10+
from typing import Optional, Union
11+
12+
13+
# https://peps.python.org/pep-0425/#python-tag
14+
INTERPRETERS = {
15+
'python': 'py',
16+
'cpython': 'cp',
17+
'pypy': 'pp',
18+
'ironpython': 'ip',
19+
'jython': 'jy',
20+
}
21+
22+
23+
def get_interpreter_tag() -> str:
24+
name = sys.implementation.name
25+
name = INTERPRETERS.get(name, name)
26+
if name in {'cp', 'pp'}:
27+
version = sys.version_info
28+
return f'{name}{version[0]}{version[1]}'
29+
return name
30+
31+
32+
def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]:
33+
value = sysconfig.get_config_var(name)
34+
if value is None:
35+
return default
36+
return value
37+
38+
39+
def _get_cpython_abi() -> str:
40+
version = sys.version_info
41+
debug = pymalloc = ''
42+
if _get_config_var('Py_DEBUG', hasattr(sys, 'gettotalrefcount')):
43+
debug = 'd'
44+
if sys.version_info < (3, 8) and _get_config_var('WITH_PYMALLOC', True):
45+
pymalloc = 'm'
46+
return f'cp{version[0]}{version[1]}{debug}{pymalloc}'
47+
48+
49+
def get_abi_tag() -> str:
50+
abi = sysconfig.get_config_var('SOABI')
51+
if not abi:
52+
# CPython on Windows does not have a SOABI sysconfig variable.
53+
assert sys.implementation.name == 'cpython'
54+
return _get_cpython_abi()
55+
if abi.startswith('cpython'):
56+
return 'cp' + abi.split('-')[1]
57+
if abi.startswith('pypy'):
58+
return '_'.join(abi.split('-')[:2])
59+
return abi.replace('.', '_').replace('-', '_')
60+
61+
62+
def _get_macosx_platform_tag() -> str:
63+
ver, x, arch = platform.mac_ver()
64+
65+
# Override the macOS version if one is provided via the
66+
# MACOS_DEPLOYMENT_TARGET environment variable. Return it
67+
# unchanged otherwise.
68+
try:
69+
version = tuple(map(int, os.environ.get('MACOS_DEPLOYMENT_TARGET', '').split('.')))[:2]
70+
except ValueError:
71+
version = None
72+
73+
if version is None:
74+
version = tuple(map(int, ver.split('.')))[:2]
75+
if version == (10, 16):
76+
# When built against an older macOS SDK, Python will
77+
# report macOS 10.16 instead of the real version.
78+
ver = subprocess.check_output(
79+
[
80+
sys.executable,
81+
'-sS',
82+
'-c',
83+
'import platform; print(platform.mac_ver()[0])'
84+
],
85+
env={'SYSTEM_VERSION_COMPAT': '0'},
86+
universal_newlines=True)
87+
version = tuple(map(int, ver.split('.')[:2]))
88+
89+
major, minor = version
90+
if major >= 11:
91+
minor = 0
92+
93+
if sys.maxsize <= 2**32:
94+
if arch.startswith('ppc'):
95+
arch = 'ppc'
96+
arch = 'i386'
97+
98+
return f'macosx_{major}_{minor}_{arch}'
99+
100+
101+
def get_platform_tag() -> str:
102+
platform = sysconfig.get_platform()
103+
if platform.startswith('macosx'):
104+
return _get_macosx_platform_tag()
105+
if sys.maxsize <= 2**32:
106+
if platform == 'linux-x86_64':
107+
return 'linux_i686'
108+
if platform == 'linux-aarch64':
109+
return 'linux_armv7l'
110+
return platform.replace('-', '_')
111+
112+
113+
class Tag:
114+
def __init__(self, interpreter: Optional[str] = None, abi: Optional[str] = None, platform: Optional[str] = None):
115+
self.interpreter = interpreter or get_interpreter_tag()
116+
self.abi = abi or get_abi_tag()
117+
self.platform = platform or get_platform_tag()
118+
119+
def __str__(self) -> str:
120+
return f'{self.interpreter}-{self.abi}-{self.platform}'

tests/conftest.py

+19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import os.path
66
import pathlib
7+
import re
78
import shutil
89
import tempfile
910

@@ -15,6 +16,24 @@
1516
import mesonpy
1617

1718

19+
def adjust_packaging_platform_tag(platform: str) -> str:
20+
# The packaging module generates overly specific platforms tags on
21+
# Linux. The platforms tags on Linux evolved over time.
22+
# meson-python uses more relaxed platform tags to maintain
23+
# compatibility with old wheel installation tools. The relaxed
24+
# platform tags match the ones generated by the wheel package.
25+
# https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/
26+
platform = re.sub(r'^manylinux(1|2010|2014|_\d+_\d+)_(.*)$', r'linux_\2', platform)
27+
28+
# When built against an older macOS SDK, Python will report macOS
29+
# 10.16 instead of the real version. Correct the tag reported by
30+
# old packaging modules that do not contain yet a workaround.
31+
# https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
32+
platform = re.sub(r'^macosx_10_16_(.*)$', r'macosx_11_0_\1', platform)
33+
34+
return platform
35+
36+
1837
package_dir = pathlib.Path(__file__).parent / 'packages'
1938

2039

tests/test_tags.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,36 @@
1212
import pytest
1313

1414
import mesonpy
15+
import mesonpy._tags
1516

17+
from .conftest import adjust_packaging_platform_tag
1618

19+
20+
# Test against the wheel tag generated by packaging module.
1721
tag = next(packaging.tags.sys_tags())
1822
ABI = tag.abi
1923
INTERPRETER = tag.interpreter
20-
PLATFORM = mesonpy._adjust_manylinux_tag(tag.platform)
24+
PLATFORM = adjust_packaging_platform_tag(tag.platform)
25+
2126
SUFFIX = sysconfig.get_config_var('EXT_SUFFIX')
2227
ABI3SUFFIX = next((x for x in mesonpy._EXTENSION_SUFFIXES if '.abi3.' in x), None)
2328

2429

30+
def test_wheel_tag():
31+
str(mesonpy._tags.Tag()) == f'{INTERPRETER}-{ABI}-{PLATFORM}'
32+
str(mesonpy._tags.Tag(abi='abi3')) == f'{INTERPRETER}-abi3-{PLATFORM}'
33+
34+
35+
@pytest.mark.skipif(platform.system() != 'Darwin', reason='macOS specific test')
36+
def test_macos_platform_tag(monkeypatch):
37+
for minor in range(9, 16):
38+
monkeypatch.setenv('MACOS_DEPLOYMENT_TARGET', f'10.{minor}')
39+
assert next(packaging.tags.mac_platforms((10, minor))) == mesonpy._tags.get_platform_tag()
40+
for major in range(11, 20):
41+
monkeypatch.setenv('MACOS_DEPLOYMENT_TARGET', f'{major}.0')
42+
assert next(packaging.tags.mac_platforms((major, 0))) == mesonpy._tags.get_platform_tag()
43+
44+
2545
def wheel_builder_test_factory(monkeypatch, content):
2646
files = defaultdict(list)
2747
files.update({key: [(pathlib.Path(x), os.path.join('build', x)) for x in value] for key, value in content.items()})

tests/test_wheel.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414

1515
import mesonpy
1616

17+
from .conftest import adjust_packaging_platform_tag
18+
1719

1820
EXT_SUFFIX = sysconfig.get_config_var('EXT_SUFFIX')
1921
INTERPRETER_VERSION = f'{sys.version_info[0]}{sys.version_info[1]}'
2022

21-
_tag = next(packaging.tags.sys_tags())
22-
ABI = _tag.abi
23-
INTERPRETER = _tag.interpreter
24-
PLATFORM = mesonpy._adjust_manylinux_tag(_tag.platform)
23+
# Test against the wheel tag generated by packaging module.
24+
tag = next(packaging.tags.sys_tags())
25+
ABI = tag.abi
26+
INTERPRETER = tag.interpreter
27+
PLATFORM = adjust_packaging_platform_tag(tag.platform)
2528

2629
if platform.system() == 'Linux':
2730
SHARED_LIB_EXT = 'so'

0 commit comments

Comments
 (0)