Skip to content

Commit 87fb324

Browse files
rgommersmgorny
authored andcommitted
WIP: ENH: implement support for build-details.json (PEP 739)
Signed-off-by: Michał Górny <mgorny@quansight.com>
1 parent bc406f7 commit 87fb324

4 files changed

Lines changed: 85 additions & 18 deletions

File tree

mesonpy/__init__.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ class _WheelBuilder():
340340
_manifest: Dict[str, List[_Entry]]
341341
_limited_api: bool
342342
_allow_windows_shared_libs: bool
343+
_is_cross: bool
344+
_build_details: mesonpy._tags.BuildDetailsDict
343345

344346
@property
345347
def _has_internal_libs(self) -> bool:
@@ -370,8 +372,8 @@ def tag(self) -> mesonpy._tags.Tag:
370372
# does not contain any extension module (does not
371373
# distribute any file in {platlib}) thus use generic
372374
# implementation and ABI tags.
373-
return mesonpy._tags.Tag('py3', 'none', None)
374-
return mesonpy._tags.Tag(None, self._stable_abi, None)
375+
return mesonpy._tags.Tag('py3', 'none', None, self._build_details)
376+
return mesonpy._tags.Tag(None, self._stable_abi, None, self._build_details)
375377

376378
@property
377379
def name(self) -> str:
@@ -834,6 +836,21 @@ def __init__(
834836
''')
835837
self._meson_native_file.write_text(native_file_data, encoding='utf-8')
836838

839+
# Handle cross compilation
840+
self._is_cross = any(s.startswith('--cross-file') for s in self._meson_args['setup'])
841+
self._build_details = None
842+
# Use build-details.json (PEP 739) to determine
843+
# platform/interpreter/abi tags, if given.
844+
for setup_arg in reversed(self._meson_args['setup']):
845+
if setup_arg.startswith('-Dpython.build_config='):
846+
with open(setup_arg.split('=', 1)[1]) as f:
847+
self._build_details = json.load(f)
848+
break
849+
else:
850+
if self._is_cross:
851+
# TODO: warn that interpreter details may be wrong. Get platform from cross file.
852+
pass
853+
837854
# reconfigure if we have a valid Meson build directory. Meson
838855
# uses the presence of the 'meson-private/coredata.dat' file
839856
# in the build directory as indication that the build
@@ -1151,13 +1168,17 @@ def sdist(self, directory: Path) -> pathlib.Path:
11511168
def wheel(self, directory: Path) -> pathlib.Path:
11521169
"""Generates a wheel in the specified directory."""
11531170
self.build()
1154-
builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
1171+
builder = _WheelBuilder(
1172+
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs,
1173+
self._is_cross, self._build_details)
11551174
return builder.build(directory)
11561175

11571176
def editable(self, directory: Path) -> pathlib.Path:
11581177
"""Generates an editable wheel in the specified directory."""
11591178
self.build()
1160-
builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
1179+
builder = _EditableWheelBuilder(
1180+
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs,
1181+
self._is_cross, self._build_details)
11611182
return builder.build(directory, self._source_dir, self._build_dir, self._build_command, self._editable_verbose)
11621183

11631184

mesonpy/_tags.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515
if typing.TYPE_CHECKING: # pragma: no cover
1616
from typing import Optional, Union
1717

18+
from mesonpy._compat import TypedDict
19+
20+
class _AbiDict(TypedDict):
21+
extension_suffix: str
22+
23+
class _ImplementationVersionDict(TypedDict):
24+
major: int
25+
minor: int
26+
27+
class _ImplementationDict(TypedDict):
28+
name: str
29+
version: _ImplementationVersionDict
30+
31+
class BuildDetailsDict(TypedDict):
32+
abi: _AbiDict
33+
implementation: _ImplementationDict
34+
platform: str
35+
1836

1937
# https://peps.python.org/pep-0425/#python-tag
2038
INTERPRETERS = {
@@ -29,11 +47,17 @@
2947
_32_BIT_INTERPRETER = struct.calcsize('P') == 4
3048

3149

32-
def get_interpreter_tag() -> str:
33-
name = sys.implementation.name
50+
def get_interpreter_tag(build_details: Optional[BuildDetailsDict] = None) -> str:
51+
if build_details is None:
52+
name = sys.implementation.name
53+
major, minor = sys.version_info[:2]
54+
else:
55+
name = build_details['implementation']['name']
56+
_v = build_details['implementation']['version']
57+
major = _v['major']
58+
minor = _v['minor']
3459
name = INTERPRETERS.get(name, name)
35-
version = sys.version_info
36-
return f'{name}{version[0]}{version[1]}'
60+
return f'{name}{major}{minor}'
3761

3862

3963
def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]:
@@ -53,7 +77,12 @@ def _get_cpython_abi() -> str:
5377
return f'cp{version[0]}{version[1]}{debug}{pymalloc}'
5478

5579

56-
def get_abi_tag() -> str:
80+
def get_abi_tag(build_details: Optional[BuildDetailsDict] = None) -> str:
81+
if build_details is not None:
82+
ext_suffix = build_details['abi']['extension_suffix']
83+
else:
84+
ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')
85+
5786
# The best solution to obtain the Python ABI is to parse the
5887
# $SOABI or $EXT_SUFFIX sysconfig variables as defined in PEP-314.
5988

@@ -62,7 +91,7 @@ def get_abi_tag() -> str:
6291
# See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
6392
# https://github.com/pypa/packaging/pull/607.
6493
try:
65-
empty, abi, ext = str(sysconfig.get_config_var('EXT_SUFFIX')).split('.')
94+
empty, abi, ext = str(ext_suffix).split('.')
6695
except ValueError as exc:
6796
# CPython <= 3.8.7 on Windows does not implement PEP3149 and
6897
# uses '.pyd' as $EXT_SUFFIX, which does not allow to extract
@@ -178,8 +207,8 @@ def _get_ios_platform_tag() -> str:
178207
return f'ios_{version[0]}_{version[1]}_{multiarch}'
179208

180209

181-
def get_platform_tag() -> str:
182-
platform = sysconfig.get_platform()
210+
def get_platform_tag(build_details: Optional[BuildDetailsDict] = None) -> str:
211+
platform = build_details['platform'] if build_details is not None else sysconfig.get_platform()
183212
if platform.startswith('macosx'):
184213
return _get_macosx_platform_tag()
185214
if platform.startswith('ios'):
@@ -194,10 +223,11 @@ def get_platform_tag() -> str:
194223

195224

196225
class Tag:
197-
def __init__(self, interpreter: Optional[str] = None, abi: Optional[str] = None, platform: Optional[str] = None):
198-
self.interpreter = interpreter or get_interpreter_tag()
199-
self.abi = abi or get_abi_tag()
200-
self.platform = platform or get_platform_tag()
226+
def __init__(self, interpreter: Optional[str] = None, abi: Optional[str] = None, platform: Optional[str] = None,
227+
build_details: Optional[BuildDetailsDict] = None):
228+
self.interpreter = interpreter or get_interpreter_tag(build_details)
229+
self.abi = abi or get_abi_tag(build_details)
230+
self.platform = platform or get_platform_tag(build_details)
201231

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

tests/test_tags.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: MIT
44

55
import importlib.machinery
6+
import json
67
import os
78
import pathlib
89
import platform
@@ -39,8 +40,18 @@ def get_abi3_suffix():
3940
return suffix
4041

4142

43+
def get_build_details_json():
44+
# Technically, this is only applicable to 3.14+, but we account for FileNotFoundError anyway.
45+
try:
46+
with open(pathlib.Path(sysconfig.get_path('stdlib')) / 'build-details.json') as f:
47+
return json.load(f)
48+
except FileNotFoundError:
49+
return None
50+
51+
4252
SUFFIX = sysconfig.get_config_var('EXT_SUFFIX')
4353
ABI3SUFFIX = get_abi3_suffix()
54+
BUILD_DETAILS_JSON = get_build_details_json()
4455

4556

4657
def test_wheel_tag():
@@ -106,7 +117,7 @@ def wheel_builder_test_factory(content, pure=True, limited_api=False):
106117
manifest = defaultdict(list)
107118
for key, value in content.items():
108119
manifest[key] = [mesonpy._Entry(pathlib.Path(x), os.path.join('build', x)) for x in value]
109-
return mesonpy._WheelBuilder(None, manifest, limited_api, False)
120+
return mesonpy._WheelBuilder(None, manifest, limited_api, False, False, None)
110121

111122

112123
def test_tag_empty_wheel():
@@ -149,3 +160,8 @@ def test_tag_mixed_abi():
149160
}, pure=False, limited_api=True)
150161
with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '):
151162
assert str(builder.tag) == f'{INTERPRETER}-abi3-{PLATFORM}'
163+
164+
165+
@pytest.mark.skipif(BUILD_DETAILS_JSON is None, reason='No build-details.json for this interpreter')
166+
def test_system_build_details():
167+
assert str(mesonpy._tags.Tag()) == str(mesonpy._tags.Tag(build_details=BUILD_DETAILS_JSON))

tests/test_wheel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ def test_entrypoints(wheel_full_metadata):
255255
def test_top_level_modules(package_module_types):
256256
with mesonpy._project() as project:
257257
builder = mesonpy._EditableWheelBuilder(
258-
project._metadata, project._manifest, project._limited_api, project._allow_windows_shared_libs)
258+
project._metadata, project._manifest, project._limited_api, project._allow_windows_shared_libs, False, None)
259259
assert set(builder._top_level_modules) == {
260260
'file',
261261
'package',

0 commit comments

Comments
 (0)