Skip to content

Commit ce79e07

Browse files
committed
Use BuildDetails for the native build case as well
Signed-off-by: Michał Górny <mgorny@quansight.com>
1 parent 08a6811 commit ce79e07

3 files changed

Lines changed: 115 additions & 80 deletions

File tree

mesonpy/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +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 | None
343+
_build_details: mesonpy._tags.BuildDetails
344344

345345
@property
346346
def _has_internal_libs(self) -> bool:
@@ -365,14 +365,14 @@ def _pure(self) -> bool:
365365
def tag(self) -> mesonpy._tags.Tag:
366366
"""Wheel tags."""
367367
if self._pure:
368-
return mesonpy._tags.Tag('py3', 'none', 'any')
368+
return mesonpy._tags.Tag('py3', 'none', 'any', build_details=self._build_details)
369369
if not self._has_extension_modules:
370370
# The wheel has platform dependent code (is not pure) but
371371
# does not contain any extension module (does not
372372
# distribute any file in {platlib}) thus use generic
373373
# implementation and ABI tags.
374-
return mesonpy._tags.Tag('py3', 'none', None, self._build_details)
375-
return mesonpy._tags.Tag(None, self._stable_abi, None, self._build_details)
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)
376376

377377
@property
378378
def name(self) -> str:
@@ -835,7 +835,7 @@ def __init__(
835835
''')
836836
self._meson_native_file.write_text(native_file_data, encoding='utf-8')
837837

838-
# Starting with version 1.10, Meson can consume a `build-detail.json`
838+
# Starting with version 1.10, Meson can consume a `build-details.json`
839839
# file following the specification is PEP 739 to obtain required
840840
# information to build extension modules without having to run the
841841
# interpreter. The path to the `build-details.json` can be specified
@@ -849,9 +849,14 @@ def __init__(
849849
for arg in reversed(args.D):
850850
name, value = arg.split('=', 1)
851851
if name == 'python.build_config':
852-
with open(value, 'r', encoding='utf8') as f:
853-
self._build_details = json.load(f)
852+
try:
853+
with open(value, 'r', encoding='utf8') as f:
854+
self._build_details = json.load(f)
855+
except OSError as err:
856+
raise ConfigError(f'The file specified as "python.build_config" cannot be opened: {err}') from err
854857
break
858+
if self._build_details is None:
859+
self._build_details = mesonpy._tags.introspect_build_details()
855860

856861
# reconfigure if we have a valid Meson build directory. Meson
857862
# uses the presence of the 'meson-private/coredata.dat' file
@@ -1344,7 +1349,7 @@ def build_editable(
13441349
if not config_settings:
13451350
config_settings = {}
13461351
if 'build-dir' not in config_settings and 'builddir' not in config_settings:
1347-
config_settings['build-dir'] = 'build/' + mesonpy._tags.get_abi_tag()
1352+
config_settings['build-dir'] = 'build/' + mesonpy._tags.get_abi_tag(mesonpy._tags.introspect_build_details())
13481353

13491354
out = pathlib.Path(wheel_directory)
13501355
with _project(config_settings) as project:

mesonpy/_tags.py

Lines changed: 97 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,99 @@ class BuildDetails(TypedDict):
4545
_32_BIT_INTERPRETER = struct.calcsize('P') == 4
4646

4747

48-
def get_interpreter_tag(build_details: BuildDetails | None = None) -> str:
49-
if build_details is None:
50-
name = sys.implementation.name
51-
major, minor = sys.version_info[:2]
52-
else:
53-
name = build_details['implementation']['name']
54-
_v = build_details['implementation']['version']
55-
major = _v['major']
56-
minor = _v['minor']
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']
57128
name = INTERPRETERS.get(name, name)
58129
return f'{name}{major}{minor}'
59130

60131

61-
def get_abi_tag(build_details: BuildDetails | None = None) -> str:
132+
def get_abi_tag(build_details: BuildDetails) -> str:
62133
# The best solution to obtain the Python ABI is to parse the
63134
# $SOABI or $EXT_SUFFIX sysconfig variables as defined in PEP-314.
64135

65136
# PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
66137
# Using $EXT_SUFFIX will not break when PyPy will fix this.
67138
# See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
68139
# https://github.com/pypa/packaging/pull/607.
69-
if build_details is None:
70-
ext_suffix = str(sysconfig.get_config_var('EXT_SUFFIX'))
71-
else:
72-
ext_suffix = build_details['abi']['extension_suffix']
140+
ext_suffix = build_details['abi']['extension_suffix']
73141
empty, abi, ext = ext_suffix.split('.')
74142

75143
# The packaging module initially based his understanding of the
@@ -91,8 +159,9 @@ def get_abi_tag(build_details: BuildDetails | None = None) -> str:
91159
return abi.replace('.', '_').replace('-', '_')
92160

93161

94-
def _get_macosx_platform_tag(build_details: BuildDetails | None = None) -> str:
95-
ver, _, arch = platform.mac_ver()
162+
def _get_macosx_platform_tag(platform: str) -> str:
163+
name, version, arch = platform.split('-', 2)
164+
assert name == 'macosx'
96165

97166
# Override the architecture with the one provided in the
98167
# _PYTHON_HOST_PLATFORM environment variable. This environment
@@ -112,24 +181,7 @@ def _get_macosx_platform_tag(build_details: BuildDetails | None = None) -> str:
112181
parts = os.environ.get('MACOSX_DEPLOYMENT_TARGET', '').split('.')[:2]
113182
version = tuple(map(int, parts + ['0'] * (2 - len(parts))))
114183
except ValueError:
115-
version = tuple(map(int, ver.split('.')))[:2]
116-
117-
# Python built with older macOS SDK on macOS 11, reports an
118-
# nonexistent macOS 10.16 version instead of the real version.
119-
#
120-
# The packaging module introduced a workaround
121-
# https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
122-
#
123-
# This results in packaging versions up to 21.3 generating
124-
# platform tags like "macosx_10_16_x86_64" and later versions
125-
# generating "macosx_11_0_x86_64". Using the latter would be more
126-
# correct but prevents the resulting wheel from being installed on
127-
# systems using packaging 21.3 or earlier (pip 22.3 or earlier).
128-
#
129-
# Fortunately packaging versions carrying the workaround still
130-
# accepts "macosx_10_16_x86_64" as a compatible platform tag. We
131-
# can therefore ignore the issue and generate the slightly
132-
# incorrect tag.
184+
version = tuple(map(int, version.split('.')[:2]))
133185

134186
# The minimum macOS ABI version on arm64 is 11.0. The macOS SDK
135187
# on arm64 silently bumps any compatibility version specified via
@@ -151,59 +203,36 @@ def _get_macosx_platform_tag(build_details: BuildDetails | None = None) -> str:
151203
# the patch level. Reset the patch level to zero.
152204
minor = 0
153205

154-
# When using build-details.json, the platform recorded should be correct
155-
# per the bitness of the interpreter.
156-
if build_details is None and _32_BIT_INTERPRETER:
157-
# 32-bit Python running on a 64-bit kernel.
158-
if arch == 'ppc64':
159-
arch = 'ppc'
160-
if arch == 'x86_64':
161-
arch = 'i386'
162-
163206
return f'macosx_{major}_{minor}_{arch}'
164207

165208

166-
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+
167213
# Override the iOS version if one is provided via the
168214
# IPHONEOS_DEPLOYMENT_TARGET environment variable.
169215
try:
170216
parts = os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', '').split('.')[:2]
171-
version = tuple(map(int, parts + ['0'] * (2 - len(parts))))
217+
version = '.'.join(map(int, parts + ['0'] * (2 - len(parts))))
172218
except ValueError:
173-
version = tuple(map(int, platform.ios_ver().release.split('.')))[:2] # type: ignore[attr-defined]
174-
175-
# Although _multiarch is an internal implementation detail, it's a core part
176-
# of how CPython is implemented on iOS; this attribute is also relied upon
177-
# by `packaging` as part of tag determination.
178-
multiarch = sys.implementation._multiarch.replace('-', '_')
219+
pass
179220

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

182223

183224
def get_platform_tag(build_details: BuildDetails | None = None) -> str:
184-
if build_details is None:
185-
platform = sysconfig.get_platform()
186-
else:
187-
platform = build_details['platform']
188-
225+
platform = build_details['platform']
189226
if platform.startswith('macosx'):
190-
return _get_macosx_platform_tag(build_details)
227+
return _get_macosx_platform_tag(platform)
191228
if platform.startswith('ios'):
192-
return _get_ios_platform_tag()
193-
# When using build-details.json, the platform recorded should be correct
194-
# per the bitness of the interpreter.
195-
if build_details is None and _32_BIT_INTERPRETER:
196-
# 32-bit Python running on a 64-bit kernel.
197-
if platform == 'linux-x86_64':
198-
return 'linux_i686'
199-
if platform == 'linux-aarch64':
200-
return 'linux_armv7l'
229+
return _get_ios_platform_tag(platform)
201230
return platform.replace('-', '_').replace('.', '_').lower()
202231

203232

204233
class Tag:
205234
def __init__(self, interpreter: str | None = None, abi: str | None = None, platform: str | None = None,
206-
build_details: BuildDetails | None = None):
235+
*, build_details: BuildDetails):
207236
self.interpreter = interpreter or get_interpreter_tag(build_details)
208237
self.abi = abi or get_abi_tag(build_details)
209238
self.platform = platform or get_platform_tag(build_details)

tests/test_tags.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ def get_abi3_suffix():
5555

5656
SUFFIX = sysconfig.get_config_var('EXT_SUFFIX')
5757
ABI3SUFFIX = get_abi3_suffix()
58+
SYSTEM_BUILD_DETAILS = mesonpy._tags.introspect_build_details()
5859

5960

6061
def test_wheel_tag():
61-
assert str(mesonpy._tags.Tag()) == f'{INTERPRETER}-{ABI}-{PLATFORM}'
62-
assert str(mesonpy._tags.Tag(abi='abi3')) == f'{INTERPRETER}-abi3-{PLATFORM}'
62+
assert str(mesonpy._tags.Tag(build_details=SYSTEM_BUILD_DETAILS)) == f'{INTERPRETER}-{ABI}-{PLATFORM}'
63+
assert str(mesonpy._tags.Tag(abi='abi3', build_details=SYSTEM_BUILD_DETAILS)) == f'{INTERPRETER}-abi3-{PLATFORM}'
6364

6465

6566
@pytest.mark.skipif(sys.platform != 'darwin', reason='macOS specific test')
@@ -120,7 +121,7 @@ def wheel_builder_test_factory(content, pure=True, limited_api=False):
120121
manifest = defaultdict(list)
121122
for key, value in content.items():
122123
manifest[key] = [mesonpy._Entry(pathlib.Path(x), os.path.join('build', x)) for x in value]
123-
return mesonpy._WheelBuilder(None, manifest, limited_api, False, None)
124+
return mesonpy._WheelBuilder(None, manifest, limited_api, False, SYSTEM_BUILD_DETAILS)
124125

125126

126127
def test_tag_empty_wheel():
@@ -171,4 +172,4 @@ def test_build_details():
171172
build_details = json.load(f)
172173
except FileNotFoundError:
173174
return pytest.skip('build-details.json not found')
174-
assert str(mesonpy._tags.Tag()) == str(mesonpy._tags.Tag(build_details=build_details))
175+
assert str(mesonpy._tags.Tag(build_details=SYSTEM_BUILD_DETAILS)) == str(mesonpy._tags.Tag(build_details=build_details))

0 commit comments

Comments
 (0)