Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ class _WheelBuilder():
_manifest: Dict[str, List[_Entry]]
_limited_api: bool
_allow_windows_shared_libs: bool
_build_details: mesonpy._tags.BuildDetails | None

@property
def _has_internal_libs(self) -> bool:
Expand Down Expand Up @@ -370,8 +371,8 @@ def tag(self) -> mesonpy._tags.Tag:
# does not contain any extension module (does not
# distribute any file in {platlib}) thus use generic
# implementation and ABI tags.
return mesonpy._tags.Tag('py3', 'none', None)
return mesonpy._tags.Tag(None, self._stable_abi, None)
return mesonpy._tags.Tag('py3', 'none', None, self._build_details)
return mesonpy._tags.Tag(None, self._stable_abi, None, self._build_details)
Comment thread
dnicolodi marked this conversation as resolved.

@property
def name(self) -> str:
Expand Down Expand Up @@ -834,6 +835,24 @@ def __init__(
''')
self._meson_native_file.write_text(native_file_data, encoding='utf-8')

# Starting with version 1.10, Meson can consume a `build-detail.json`
# file following the specification is PEP 739 to obtain required
# information to build extension modules without having to run the
# interpreter. The path to the `build-details.json` can be specified
# passing the with the `-Dpython.build_config=` option to `meson
# setup`. Extract the value passed to this option and use the details
# in the `build-details.json` file to compute the wheel tag.
self._build_details: mesonpy._tags.BuildDetails | None = None
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-D', action='append', default=[])
args, _ = parser.parse_known_args(self._meson_args['setup'])
for arg in reversed(args.D):
name, value = arg.split('=', 1)
if name == 'python.build_config':
with open(value, 'r', encoding='utf8') as f:
self._build_details = json.load(f)
break

# reconfigure if we have a valid Meson build directory. Meson
# uses the presence of the 'meson-private/coredata.dat' file
# in the build directory as indication that the build
Expand Down Expand Up @@ -1151,13 +1170,15 @@ def sdist(self, directory: Path) -> pathlib.Path:
def wheel(self, directory: Path) -> pathlib.Path:
"""Generates a wheel in the specified directory."""
self.build()
builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
builder = _WheelBuilder(
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs, self._build_details)
return builder.build(directory)

def editable(self, directory: Path) -> pathlib.Path:
"""Generates an editable wheel in the specified directory."""
self.build()
builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs)
builder = _EditableWheelBuilder(
self._metadata, self._manifest, self._limited_api, self._allow_windows_shared_libs, self._build_details)
return builder.build(directory, self._source_dir, self._build_dir, self._build_command, self._editable_verbose)


Expand Down
60 changes: 48 additions & 12 deletions mesonpy/_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
import struct
import sys
import sysconfig
import typing


if typing.TYPE_CHECKING: # pragma: no cover
from typing import TypedDict

class _Abi(TypedDict):
extension_suffix: str

class _ImplementationVersion(TypedDict):
major: int
minor: int

class _Implementation(TypedDict):
name: str
version: _ImplementationVersion

class BuildDetails(TypedDict):
abi: _Abi
implementation: _Implementation
platform: str


# https://peps.python.org/pep-0425/#python-tag
Expand All @@ -24,22 +45,32 @@
_32_BIT_INTERPRETER = struct.calcsize('P') == 4


def get_interpreter_tag() -> str:
name = sys.implementation.name
def get_interpreter_tag(build_details: BuildDetails | None = None) -> str:
if build_details is None:
name = sys.implementation.name
major, minor = sys.version_info[:2]
else:
name = build_details['implementation']['name']
_v = build_details['implementation']['version']
major = _v['major']
minor = _v['minor']
name = INTERPRETERS.get(name, name)
version = sys.version_info
return f'{name}{version[0]}{version[1]}'
return f'{name}{major}{minor}'


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

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

# The packaging module initially based his understanding of the
# $SOABI variable on the inconsistent value reported by PyPy, and
Expand Down Expand Up @@ -147,8 +178,12 @@ def _get_ios_platform_tag() -> str:
return f'ios_{version[0]}_{version[1]}_{multiarch}'


def get_platform_tag() -> str:
platform = sysconfig.get_platform()
def get_platform_tag(build_details: BuildDetails | None = None) -> str:
if build_details is None:
platform = sysconfig.get_platform()
else:
platform = build_details['platform']

if platform.startswith('macosx'):
return _get_macosx_platform_tag()
if platform.startswith('ios'):
Expand All @@ -163,10 +198,11 @@ def get_platform_tag() -> str:


class Tag:
def __init__(self, interpreter: str | None = None, abi: str | None = None, platform: str | None = None):
self.interpreter = interpreter or get_interpreter_tag()
self.abi = abi or get_abi_tag()
self.platform = platform or get_platform_tag()
def __init__(self, interpreter: str | None = None, abi: str | None = None, platform: str | None = None,
build_details: BuildDetails | None = None):
self.interpreter = interpreter or get_interpreter_tag(build_details)
self.abi = abi or get_abi_tag(build_details)
self.platform = platform or get_platform_tag(build_details)

def __str__(self) -> str:
return f'{self.interpreter}-{self.abi}-{self.platform}'
12 changes: 11 additions & 1 deletion tests/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT

import importlib.machinery
import json
import os
import pathlib
import platform
Expand Down Expand Up @@ -106,7 +107,7 @@ def wheel_builder_test_factory(content, pure=True, limited_api=False):
manifest = defaultdict(list)
for key, value in content.items():
manifest[key] = [mesonpy._Entry(pathlib.Path(x), os.path.join('build', x)) for x in value]
return mesonpy._WheelBuilder(None, manifest, limited_api, False)
return mesonpy._WheelBuilder(None, manifest, limited_api, False, None)


def test_tag_empty_wheel():
Expand Down Expand Up @@ -149,3 +150,12 @@ def test_tag_mixed_abi():
}, pure=False, limited_api=True)
with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '):
assert str(builder.tag) == f'{INTERPRETER}-abi3-{PLATFORM}'


def test_build_details():
try:
with open(os.path.join(sysconfig.get_path('stdlib'), 'build-details.json'), encoding='utf8') as f:
build_details = json.load(f)
except FileNotFoundError:
return pytest.skip('build-details.json not found')
assert str(mesonpy._tags.Tag()) == str(mesonpy._tags.Tag(build_details=build_details))
2 changes: 1 addition & 1 deletion tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def test_entrypoints(wheel_full_metadata):
def test_top_level_modules(package_module_types):
with mesonpy._project() as project:
builder = mesonpy._EditableWheelBuilder(
project._metadata, project._manifest, project._limited_api, project._allow_windows_shared_libs)
project._metadata, project._manifest, project._limited_api, project._allow_windows_shared_libs, None)
assert set(builder._top_level_modules) == {
'file',
'package',
Expand Down
Loading