Skip to content

Commit 8892a9b

Browse files
rgommersmgorny
authored andcommitted
ENH: implement support for build-details.json (PEP 739)
1 parent 43fff4a commit 8892a9b

5 files changed

Lines changed: 233 additions & 77 deletions

File tree

mesonpy/__init__.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ class _WheelBuilder():
340340
_manifest: Dict[str, List[_Entry]]
341341
_limited_api: bool
342342
_allow_windows_shared_libs: bool
343+
_build_details: mesonpy._tags.BuildDetails
343344

344345
@property
345346
def _has_internal_libs(self) -> bool:
@@ -364,14 +365,14 @@ def _pure(self) -> bool:
364365
def tag(self) -> mesonpy._tags.Tag:
365366
"""Wheel tags."""
366367
if self._pure:
367-
return mesonpy._tags.Tag('py3', 'none', 'any')
368+
return mesonpy._tags.Tag('py3', 'none', 'any', build_details=self._build_details)
368369
if not self._has_extension_modules:
369370
# The wheel has platform dependent code (is not pure) but
370371
# does not contain any extension module (does not
371372
# distribute any file in {platlib}) thus use generic
372373
# implementation and ABI tags.
373-
return mesonpy._tags.Tag('py3', 'none', None)
374-
return mesonpy._tags.Tag(None, self._stable_abi, None)
374+
return mesonpy._tags.Tag('py3', 'none', None, build_details=self._build_details)
375+
return mesonpy._tags.Tag(None, self._stable_abi, None, build_details=self._build_details)
375376

376377
@property
377378
def name(self) -> str:
@@ -844,6 +845,27 @@ def __init__(
844845
''')
845846
self._meson_native_file.write_text(native_file_data, encoding='utf-8')
846847

848+
# Starting with version 1.10, Meson can consume a `build-details.json`
849+
# file following the specification is PEP 739 to obtain required
850+
# information to build extension modules without having to run the
851+
# interpreter. The path to the `build-details.json` can be specified
852+
# passing the with the `-Dpython.build_config=` option to `meson
853+
# setup`. Extract the value passed to this option and use the details
854+
# in the `build-details.json` file to compute the wheel tag.
855+
self._build_details: mesonpy._tags.BuildDetails = mesonpy._tags.introspect_build_details()
856+
parser = argparse.ArgumentParser(add_help=False)
857+
parser.add_argument('-D', action='append', default=[])
858+
args, _ = parser.parse_known_args(self._meson_args['setup'])
859+
for arg in reversed(args.D):
860+
name, value = arg.split('=', 1)
861+
if name == 'python.build_config':
862+
try:
863+
with open(value, 'r', encoding='utf8') as f:
864+
self._build_details = json.load(f)
865+
except OSError as err:
866+
raise ConfigError(f'The file specified as "python.build_config" cannot be opened: {err}') from err
867+
break
868+
847869
# reconfigure if we have a valid Meson build directory. Meson
848870
# uses the presence of the 'meson-private/coredata.dat' file
849871
# in the build directory as indication that the build
@@ -1161,13 +1183,15 @@ def sdist(self, directory: Path) -> pathlib.Path:
11611183
def wheel(self, directory: Path) -> pathlib.Path:
11621184
"""Generates a wheel in the specified directory."""
11631185
self.build()
1164-
builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
1186+
builder = _WheelBuilder(
1187+
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs, self._build_details)
11651188
return builder.build(directory)
11661189

11671190
def editable(self, directory: Path) -> pathlib.Path:
11681191
"""Generates an editable wheel in the specified directory."""
11691192
self.build()
1170-
builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
1193+
builder = _EditableWheelBuilder(
1194+
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs, self._build_details)
11711195
return builder.build(directory, self._source_dir, self._build_dir, self._build_command, self._editable_verbose)
11721196

11731197

@@ -1333,7 +1357,7 @@ def build_editable(
13331357
if not config_settings:
13341358
config_settings = {}
13351359
if 'build-dir' not in config_settings and 'builddir' not in config_settings:
1336-
config_settings['build-dir'] = 'build/' + mesonpy._tags.get_abi_tag()
1360+
config_settings['build-dir'] = 'build/' + mesonpy._tags.get_abi_tag(mesonpy._tags.introspect_build_details())
13371361

13381362
out = pathlib.Path(wheel_directory)
13391363
with _project(config_settings) as project:

mesonpy/_tags.py

Lines changed: 125 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@
99
import struct
1010
import sys
1111
import sysconfig
12+
import typing
13+
14+
15+
if typing.TYPE_CHECKING: # pragma: no cover
16+
from typing import TypedDict
17+
18+
class _Abi(TypedDict):
19+
extension_suffix: str
20+
21+
class _ImplementationVersion(TypedDict):
22+
major: int
23+
minor: int
24+
25+
class _Implementation(TypedDict):
26+
name: str
27+
version: _ImplementationVersion
28+
29+
class BuildDetails(TypedDict):
30+
abi: _Abi
31+
implementation: _Implementation
32+
platform: str
1233

1334

1435
# https://peps.python.org/pep-0425/#python-tag
@@ -24,22 +45,100 @@
2445
_32_BIT_INTERPRETER = struct.calcsize('P') == 4
2546

2647

27-
def get_interpreter_tag() -> str:
28-
name = sys.implementation.name
48+
def _get_macosx_platform() -> str:
49+
ver, _, arch = platform.mac_ver()
50+
major, minor = map(int, ver.split('.')[:2])
51+
52+
# Python built with older macOS SDK on macOS 11, reports an
53+
# nonexistent macOS 10.16 version instead of the real version.
54+
#
55+
# The packaging module introduced a workaround
56+
# https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
57+
#
58+
# This results in packaging versions up to 21.3 generating
59+
# platform tags like "macosx_10_16_x86_64" and later versions
60+
# generating "macosx_11_0_x86_64". Using the latter would be more
61+
# correct but prevents the resulting wheel from being installed on
62+
# systems using packaging 21.3 or earlier (pip 22.3 or earlier).
63+
#
64+
# Fortunately packaging versions carrying the workaround still
65+
# accepts "macosx_10_16_x86_64" as a compatible platform tag. We
66+
# can therefore ignore the issue and generate the slightly
67+
# incorrect tag.
68+
69+
if _32_BIT_INTERPRETER:
70+
# 32-bit Python running on a 64-bit kernel.
71+
if arch == 'ppc64':
72+
arch = 'ppc'
73+
if arch == 'x86_64':
74+
arch = 'i386'
75+
76+
return f'macosx-{major}.{minor}-{arch}'
77+
78+
79+
def _get_ios_platform() -> str:
80+
ver = platform.ios_ver().release
81+
major, minor = map(int, ver.split('.')[:2])
82+
83+
# Although _multiarch is an internal implementation detail, it's a core part
84+
# of how CPython is implemented on iOS; this attribute is also relied upon
85+
# by `packaging` as part of tag determination.
86+
multiarch = sys.implementation._multiarch.replace('-', '_')
87+
88+
return f'ios-{major}.{minor}-{multiarch}'
89+
90+
91+
def introspect_build_details() -> BuildDetails:
92+
platform = sysconfig.get_platform()
93+
if platform.startswith('macosx'):
94+
platform = _get_macosx_platform()
95+
elif platform.startswith('ios'):
96+
platform = _get_ios_platform()
97+
elif _32_BIT_INTERPRETER:
98+
# 32-bit Python running on a 64-bit kernel.
99+
if platform == 'linux-x86_64':
100+
platform = 'linux_i686'
101+
if platform == 'linux-aarch64':
102+
platform = 'linux_armv7l'
103+
104+
return {
105+
'abi': {
106+
# PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
107+
# Using $EXT_SUFFIX will not break when PyPy will fix this.
108+
# See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
109+
# https://github.com/pypa/packaging/pull/607.
110+
'extension_suffix': str(sysconfig.get_config_var('EXT_SUFFIX')),
111+
},
112+
'implementation': {
113+
'name': sys.implementation.name,
114+
'version': {
115+
'major': sys.version_info.major,
116+
'minor': sys.version_info.minor,
117+
},
118+
},
119+
'platform': platform,
120+
}
121+
122+
123+
def get_interpreter_tag(build_details: BuildDetails) -> str:
124+
name = build_details['implementation']['name']
125+
_v = build_details['implementation']['version']
126+
major = _v['major']
127+
minor = _v['minor']
29128
name = INTERPRETERS.get(name, name)
30-
version = sys.version_info
31-
return f'{name}{version[0]}{version[1]}'
129+
return f'{name}{major}{minor}'
32130

33131

34-
def get_abi_tag() -> str:
132+
def get_abi_tag(build_details: BuildDetails) -> str:
35133
# The best solution to obtain the Python ABI is to parse the
36134
# $SOABI or $EXT_SUFFIX sysconfig variables as defined in PEP-314.
37135

38136
# PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
39137
# Using $EXT_SUFFIX will not break when PyPy will fix this.
40138
# See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
41139
# https://github.com/pypa/packaging/pull/607.
42-
empty, abi, ext = str(sysconfig.get_config_var('EXT_SUFFIX')).split('.')
140+
ext_suffix = build_details['abi']['extension_suffix']
141+
empty, abi, ext = ext_suffix.split('.')
43142

44143
# The packaging module initially based his understanding of the
45144
# $SOABI variable on the inconsistent value reported by PyPy, and
@@ -60,8 +159,9 @@ def get_abi_tag() -> str:
60159
return abi.replace('.', '_').replace('-', '_')
61160

62161

63-
def _get_macosx_platform_tag() -> str:
64-
ver, _, arch = platform.mac_ver()
162+
def _get_macosx_platform_tag(platform: str) -> str:
163+
name, ver, arch = platform.split('-', 2)
164+
assert name == 'macosx'
65165

66166
# Override the architecture with the one provided in the
67167
# _PYTHON_HOST_PLATFORM environment variable. This environment
@@ -81,24 +181,7 @@ def _get_macosx_platform_tag() -> str:
81181
parts = os.environ.get('MACOSX_DEPLOYMENT_TARGET', '').split('.')[:2]
82182
version = tuple(map(int, parts + ['0'] * (2 - len(parts))))
83183
except ValueError:
84-
version = tuple(map(int, ver.split('.')))[:2]
85-
86-
# Python built with older macOS SDK on macOS 11, reports an
87-
# nonexistent macOS 10.16 version instead of the real version.
88-
#
89-
# The packaging module introduced a workaround
90-
# https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
91-
#
92-
# This results in packaging versions up to 21.3 generating
93-
# platform tags like "macosx_10_16_x86_64" and later versions
94-
# generating "macosx_11_0_x86_64". Using the latter would be more
95-
# correct but prevents the resulting wheel from being installed on
96-
# systems using packaging 21.3 or earlier (pip 22.3 or earlier).
97-
#
98-
# Fortunately packaging versions carrying the workaround still
99-
# accepts "macosx_10_16_x86_64" as a compatible platform tag. We
100-
# can therefore ignore the issue and generate the slightly
101-
# incorrect tag.
184+
version = tuple(map(int, ver.split('.')[:2]))
102185

103186
# The minimum macOS ABI version on arm64 is 11.0. The macOS SDK
104187
# on arm64 silently bumps any compatibility version specified via
@@ -120,53 +203,39 @@ def _get_macosx_platform_tag() -> str:
120203
# the patch level. Reset the patch level to zero.
121204
minor = 0
122205

123-
if _32_BIT_INTERPRETER:
124-
# 32-bit Python running on a 64-bit kernel.
125-
if arch == 'ppc64':
126-
arch = 'ppc'
127-
if arch == 'x86_64':
128-
arch = 'i386'
129-
130206
return f'macosx_{major}_{minor}_{arch}'
131207

132208

133-
def _get_ios_platform_tag() -> str:
209+
def _get_ios_platform_tag(platform: str) -> str:
210+
name, version, multiarch = platform.split('-', 2)
211+
assert name == 'ios'
212+
134213
# Override the iOS version if one is provided via the
135214
# IPHONEOS_DEPLOYMENT_TARGET environment variable.
136215
try:
137216
parts = os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', '').split('.')[:2]
138-
version = tuple(map(int, parts + ['0'] * (2 - len(parts))))
217+
version = '.'.join(parts + ['0'] * (2 - len(parts)))
139218
except ValueError:
140-
version = tuple(map(int, platform.ios_ver().release.split('.')))[:2] # type: ignore[attr-defined]
141-
142-
# Although _multiarch is an internal implementation detail, it's a core part
143-
# of how CPython is implemented on iOS; this attribute is also relied upon
144-
# by `packaging` as part of tag determination.
145-
multiarch = sys.implementation._multiarch.replace('-', '_')
219+
pass
146220

147-
return f'ios_{version[0]}_{version[1]}_{multiarch}'
221+
return f'ios_{version.replace(".", "_")}_{multiarch.replace("-", "_")}'
148222

149223

150-
def get_platform_tag() -> str:
151-
platform = sysconfig.get_platform()
224+
def get_platform_tag(build_details: BuildDetails) -> str:
225+
platform = build_details['platform']
152226
if platform.startswith('macosx'):
153-
return _get_macosx_platform_tag()
227+
return _get_macosx_platform_tag(platform)
154228
if platform.startswith('ios'):
155-
return _get_ios_platform_tag()
156-
if _32_BIT_INTERPRETER:
157-
# 32-bit Python running on a 64-bit kernel.
158-
if platform == 'linux-x86_64':
159-
return 'linux_i686'
160-
if platform == 'linux-aarch64':
161-
return 'linux_armv7l'
229+
return _get_ios_platform_tag(platform)
162230
return platform.replace('-', '_').replace('.', '_').lower()
163231

164232

165233
class Tag:
166-
def __init__(self, interpreter: str | None = None, abi: str | None = None, platform: str | None = None):
167-
self.interpreter = interpreter or get_interpreter_tag()
168-
self.abi = abi or get_abi_tag()
169-
self.platform = platform or get_platform_tag()
234+
def __init__(self, interpreter: str | None = None, abi: str | None = None, platform: str | None = None,
235+
*, build_details: BuildDetails):
236+
self.interpreter = interpreter or get_interpreter_tag(build_details)
237+
self.abi = abi or get_abi_tag(build_details)
238+
self.platform = platform or get_platform_tag(build_details)
170239

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

tests/test_project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mesonpy._util import chdir
2727

2828
from .conftest import MESON_VERSION, in_git_repo_context, metadata, package_dir
29+
from .test_tags import SYSTEM_BUILD_DETAILS
2930

3031

3132
def test_unsupported_python_version(package_unsupported_python_version):
@@ -358,7 +359,7 @@ def test_archflags_envvar_parsing(package_purelib_and_platlib, monkeypatch, arch
358359
monkeypatch.setenv('ARCHFLAGS', archflags)
359360
arch = archflags.split()[-1]
360361
with mesonpy._project():
361-
assert mesonpy._tags.Tag().platform.endswith(arch)
362+
assert mesonpy._tags.Tag(build_details=SYSTEM_BUILD_DETAILS).platform.endswith(arch)
362363
finally:
363364
# revert environment variable setting done by the in-process build
364365
os.environ.pop('_PYTHON_HOST_PLATFORM', None)

0 commit comments

Comments
 (0)