Skip to content

Commit 7b7b9ee

Browse files
committed
Delegate computing wheel tags to the packaging module
Use the wheel contents only to determine whether the wheel contains python ABI dependent modules or other platform dependent code. Fixes mesonbuild#189, mesonbuild#190.
1 parent 66e9427 commit 7b7b9ee

File tree

3 files changed

+39
-327
lines changed

3 files changed

+39
-327
lines changed

mesonpy/__init__.py

+34-163
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import collections
1414
import contextlib
1515
import functools
16+
import importlib.machinery
1617
import io
1718
import itertools
1819
import json
@@ -35,11 +36,11 @@
3536
Union
3637
)
3738

39+
import packaging.tags
3840
import tomli
3941

4042
import mesonpy._compat
4143
import mesonpy._elf
42-
import mesonpy._tags
4344
import mesonpy._util
4445

4546
from mesonpy._compat import Collection, Iterator, Mapping, Path
@@ -98,9 +99,8 @@ def _init_colors() -> Dict[str, str]:
9899
_STYLES = _init_colors() # holds the color values, should be _COLORS or _NO_COLORS
99100

100101

101-
_LINUX_NATIVE_MODULE_REGEX = re.compile(r'^(?P<name>.+)\.(?P<tag>.+)\.so$')
102-
_WINDOWS_NATIVE_MODULE_REGEX = re.compile(r'^(?P<name>.+)\.(?P<tag>.+)\.pyd$')
103-
_STABLE_ABI_TAG_REGEX = re.compile(r'^abi(?P<abi_number>[0-9]+)$')
102+
_EXTENSION_SUFFIXES = frozenset(s.lstrip('.') for s in importlib.machinery.EXTENSION_SUFFIXES)
103+
_EXTENSION_SUFFIX_REGEX = re.compile(r'^(:?(?P<abi[^.])\.)?(so|pyd)$')
104104

105105

106106
def _showwarning(
@@ -175,6 +175,10 @@ def _wheel_files(self) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]:
175175
def _has_internal_libs(self) -> bool:
176176
return bool(self._wheel_files['mesonpy-libs'])
177177

178+
@property
179+
def _has_extension_modules(self) -> bool:
180+
return bool(self._wheel_files['platlib'])
181+
178182
@property
179183
def basename(self) -> str:
180184
"""Normalized wheel name and version (eg. meson_python-1.0.0)."""
@@ -183,14 +187,24 @@ def basename(self) -> str:
183187
version=self._project.version,
184188
)
185189

190+
@property
191+
def tags(self) -> str:
192+
"""Wheel tags."""
193+
if self.is_pure:
194+
return 'py3-none-any'
195+
tags = next(packaging.tags.sys_tags())
196+
if not self._has_extension_modules:
197+
tags.abi = 'none'
198+
elif self._use_stable_abi:
199+
tags.abi = 'abi3'
200+
return tags
201+
186202
@property
187203
def name(self) -> str:
188204
"""Wheel name, this includes the basename and tags."""
189-
return '{basename}-{python_tag}-{abi_tag}-{platform_tag}'.format(
205+
return '{basename}-{tags}'.format(
190206
basename=self.basename,
191-
python_tag=self.python_tag,
192-
abi_tag=self.abi_tag,
193-
platform_tag=self.platform_tag,
207+
tags=str(self.tags),
194208
)
195209

196210
@property
@@ -225,7 +239,7 @@ def wheel(self) -> bytes: # noqa: F811
225239
Tag: {tags}
226240
''').strip().format(
227241
is_purelib='true' if self.is_pure else 'false',
228-
tags=f'{self.python_tag}-{self.abi_tag}-{self.platform_tag}',
242+
tags=self.tags,
229243
).encode()
230244

231245
@property
@@ -263,166 +277,23 @@ def _debian_python(self) -> bool:
263277
except ModuleNotFoundError:
264278
return False
265279

266-
@property
267-
def python_tag(self) -> str:
268-
selected_tag = self._select_abi_tag()
269-
if selected_tag and selected_tag.python:
270-
return selected_tag.python
271-
return 'py3'
272-
273-
@property
274-
def abi_tag(self) -> str:
275-
selected_tag = self._select_abi_tag()
276-
if selected_tag:
277-
return selected_tag.abi
278-
return 'none'
279-
280-
@cached_property
281-
def platform_tag(self) -> str:
282-
if self.is_pure:
283-
return 'any'
284-
# XXX: Choose the sysconfig platform here and let something like auditwheel
285-
# fix it later if there are system dependencies (eg. replace it with a manylinux tag)
286-
platform_ = sysconfig.get_platform()
287-
parts = platform_.split('-')
288-
if parts[0] == 'macosx':
289-
target = os.environ.get('MACOSX_DEPLOYMENT_TARGET')
290-
if target:
291-
print(
292-
'{yellow}MACOSX_DEPLOYMENT_TARGET is set so we are setting the '
293-
'platform tag to {target}{reset}'.format(target=target, **_STYLES)
294-
)
295-
parts[1] = target
296-
else:
297-
# If no target macOS version is specified fallback to
298-
# platform.mac_ver() instead of sysconfig.get_platform() as the
299-
# latter specifies the target macOS version Python was built
300-
# against.
301-
parts[1] = platform.mac_ver()[0]
302-
if parts[1] >= '11':
303-
# Only pick up the major version, which changed from 10.X
304-
# to X.0 from macOS 11 onwards. See
305-
# https://github.com/FFY00/meson-python/issues/160
306-
parts[1] = parts[1].split('.')[0]
307-
308-
if parts[1] in ('11', '12'):
309-
# Workaround for bug where pypa/packaging does not consider macOS
310-
# tags without minor versions valid. Some Python flavors (Homebrew
311-
# for example) on macOS started to do this in version 11, and
312-
# pypa/packaging should handle things correctly from version 13 and
313-
# forward, so we will add a 0 minor version to MacOS 11 and 12.
314-
# https://github.com/FFY00/meson-python/issues/91
315-
# https://github.com/pypa/packaging/issues/578
316-
parts[1] += '.0'
317-
318-
platform_ = '-'.join(parts)
319-
elif parts[0] == 'linux' and parts[1] == 'x86_64' and sys.maxsize == 0x7fffffff:
320-
# 32-bit Python running on an x86_64 host
321-
# https://github.com/FFY00/meson-python/issues/123
322-
parts[1] = 'i686'
323-
platform_ = '-'.join(parts)
324-
return platform_.replace('-', '_').replace('.', '_')
325-
326-
def _calculate_file_abi_tag_heuristic_windows(self, filename: str) -> Optional[mesonpy._tags.Tag]:
327-
"""Try to calculate the Windows tag from the Python extension file name."""
328-
match = _WINDOWS_NATIVE_MODULE_REGEX.match(filename)
329-
if not match:
330-
return None
331-
tag = match.group('tag')
332-
333-
try:
334-
return mesonpy._tags.StableABITag(tag)
335-
except ValueError:
336-
return mesonpy._tags.InterpreterTag(tag)
337-
338-
def _calculate_file_abi_tag_heuristic_posix(self, filename: str) -> Optional[mesonpy._tags.Tag]:
339-
"""Try to calculate the Posix tag from the Python extension file name."""
340-
# sysconfig is not guaranted to export SHLIB_SUFFIX but let's be
341-
# preventive and check its value to make sure it matches our expectations
342-
try:
343-
extension = sysconfig.get_config_vars().get('SHLIB_SUFFIX', '.so')
344-
if extension != '.so':
345-
raise NotImplementedError(
346-
f"We don't currently support the {extension} extension. "
347-
'Please report this to https://github.com/FFY00/mesonpy/issues '
348-
'and include information about your operating system.'
349-
)
350-
except KeyError:
351-
warnings.warn(
352-
'sysconfig does not export SHLIB_SUFFIX, so we are unable to '
353-
'perform the sanity check regarding the extension suffix. '
354-
'Please report this to https://github.com/FFY00/mesonpy/issues '
355-
'and include the output of `python -m sysconfig`.'
356-
)
357-
match = _LINUX_NATIVE_MODULE_REGEX.match(filename)
358-
if not match: # this file does not appear to be a native module
359-
return None
360-
tag = match.group('tag')
361-
362-
try:
363-
return mesonpy._tags.StableABITag(tag)
364-
except ValueError:
365-
return mesonpy._tags.InterpreterTag(tag)
366-
367-
def _calculate_file_abi_tag_heuristic(self, filename: str) -> Optional[mesonpy._tags.Tag]:
368-
"""Try to calculate the ABI tag from the Python extension file name."""
369-
if os.name == 'nt':
370-
return self._calculate_file_abi_tag_heuristic_windows(filename)
371-
# everything else *should* follow the POSIX way, at least to my knowledge
372-
return self._calculate_file_abi_tag_heuristic_posix(filename)
373-
374280
def _file_list_repr(self, files: Collection[str], prefix: str = '\t\t', max_count: int = 3) -> str:
375281
if len(files) > max_count:
376282
files = list(itertools.islice(files, max_count)) + [f'(... +{len(files)}))']
377283
return ''.join(f'{prefix}- {file}\n' for file in files)
378284

379-
def _files_by_tag(self) -> Mapping[mesonpy._tags.Tag, Collection[str]]:
380-
"""Map files into ABI tags."""
381-
files_by_tag: Dict[mesonpy._tags.Tag, List[str]] = collections.defaultdict(list)
382-
383-
for _, file in self._wheel_files['platlib']:
384-
# if in platlib, calculate the ABI tag
385-
tag = self._calculate_file_abi_tag_heuristic(file)
386-
if tag:
387-
files_by_tag[tag].append(file)
388-
389-
return files_by_tag
390-
391-
def _select_abi_tag(self) -> Optional[mesonpy._tags.Tag]: # noqa: C901
392-
"""Given a list of ABI tags, selects the most specific one.
285+
def _extension_abi_tag(path: pathlib.Path) -> str:
286+
"""Extract the PEP 3149 ABI tag from the extension file path, if any is present."""
287+
# The file path cannot contain a dot.
288+
name, suffix = path.name.split('.', 1)
289+
if suffix not in _EXTENSION_SUFFIXES:
290+
raise ValueError('Extension module "{}" not compatible with Python interpreter.'.format(str(p)))
291+
return = _EXTENSION_SUFFIX_REGEX.match(suffix).group('abi')
393292

394-
Raises an error if there are incompatible tags.
395-
"""
396-
# Possibilities:
397-
# - interpreter specific (cpython/pypy/etc, version)
398-
# - stable abi (abiX)
399-
tags = self._files_by_tag()
400-
selected_tag = None
401-
for tag, files in tags.items():
402-
# no selected tag yet, let's assign this one
403-
if not selected_tag:
404-
selected_tag = tag
405-
# interpreter tag
406-
elif isinstance(tag, mesonpy._tags.InterpreterTag):
407-
if tag != selected_tag:
408-
if isinstance(selected_tag, mesonpy._tags.InterpreterTag):
409-
raise ValueError(
410-
'Found files with incompatible ABI tags:\n'
411-
+ self._file_list_repr(tags[selected_tag])
412-
+ '\tand\n'
413-
+ self._file_list_repr(files)
414-
)
415-
selected_tag = tag
416-
# stable ABI
417-
elif isinstance(tag, mesonpy._tags.StableABITag):
418-
if isinstance(selected_tag, mesonpy._tags.StableABITag) and tag != selected_tag:
419-
raise ValueError(
420-
'Found files with incompatible ABI tags:\n'
421-
+ self._file_list_repr(tags[selected_tag])
422-
+ '\tand\n'
423-
+ self._file_list_repr(files)
424-
)
425-
return selected_tag
293+
@cached_property
294+
def _use_stable_abi(self) -> bool:
295+
"""Determine wether the package is compatible with the stabe ABI."""
296+
return all(_extension_abi_tag(path) == 'abi3' for path, _ in self._wheel_files['platlib'])
426297

427298
def _is_native(self, file: Union[str, pathlib.Path]) -> bool:
428299
"""Check if file is a native file."""

mesonpy/_tags.py

-134
This file was deleted.

0 commit comments

Comments
 (0)