13
13
import collections
14
14
import contextlib
15
15
import functools
16
+ import importlib .machinery
16
17
import io
17
18
import itertools
18
19
import json
35
36
Union
36
37
)
37
38
39
+ import packaging .tags
38
40
import tomli
39
41
40
42
import mesonpy ._compat
41
43
import mesonpy ._elf
42
- import mesonpy ._tags
43
44
import mesonpy ._util
44
45
45
46
from mesonpy ._compat import Collection , Iterator , Mapping , Path
@@ -98,9 +99,8 @@ def _init_colors() -> Dict[str, str]:
98
99
_STYLES = _init_colors () # holds the color values, should be _COLORS or _NO_COLORS
99
100
100
101
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)$' )
104
104
105
105
106
106
def _showwarning (
@@ -175,6 +175,10 @@ def _wheel_files(self) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]:
175
175
def _has_internal_libs (self ) -> bool :
176
176
return bool (self ._wheel_files ['mesonpy-libs' ])
177
177
178
+ @property
179
+ def _has_extension_modules (self ) -> bool :
180
+ return bool (self ._wheel_files ['platlib' ])
181
+
178
182
@property
179
183
def basename (self ) -> str :
180
184
"""Normalized wheel name and version (eg. meson_python-1.0.0)."""
@@ -183,14 +187,24 @@ def basename(self) -> str:
183
187
version = self ._project .version ,
184
188
)
185
189
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
+
186
202
@property
187
203
def name (self ) -> str :
188
204
"""Wheel name, this includes the basename and tags."""
189
- return '{basename}-{python_tag}-{abi_tag}-{platform_tag }' .format (
205
+ return '{basename}-{tags }' .format (
190
206
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 ),
194
208
)
195
209
196
210
@property
@@ -225,7 +239,7 @@ def wheel(self) -> bytes: # noqa: F811
225
239
Tag: {tags}
226
240
''' ).strip ().format (
227
241
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 ,
229
243
).encode ()
230
244
231
245
@property
@@ -263,166 +277,23 @@ def _debian_python(self) -> bool:
263
277
except ModuleNotFoundError :
264
278
return False
265
279
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
-
374
280
def _file_list_repr (self , files : Collection [str ], prefix : str = '\t \t ' , max_count : int = 3 ) -> str :
375
281
if len (files ) > max_count :
376
282
files = list (itertools .islice (files , max_count )) + [f'(... +{ len (files )} ))' ]
377
283
return '' .join (f'{ prefix } - { file } \n ' for file in files )
378
284
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' )
393
292
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
- + '\t and\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
- + '\t and\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' ])
426
297
427
298
def _is_native (self , file : Union [str , pathlib .Path ]) -> bool :
428
299
"""Check if file is a native file."""
0 commit comments