From 00595bf726c0b813c0f5e0945bf9e5e8a0033744 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Mon, 19 Oct 2026 14:50:28 -0600 Subject: [PATCH] Use sysconfig data for wheel tags and stable-ABI validation Wheel tags and limited-API validation currently mix sysconfig values with the running interpreter's version, implementation and pointer size. Cross builds can therefore emit host-specific tags or reject stable-ABI wheels supported by the target. Derive interpreter details from sysconfig, retaining best-effort fallbacks when configuration data is unavailable. Fall back to SOABI when VERSION is missing or invalid, and use EXT_SUFFIX for Windows debug builds when Py_DEBUG is unavailable. Validate tags against the configured interpreter's version and ABI instead of tags.sys_tags(), preserving platform overrides and compressed ABI3/ABI3T tags. Cover differing host and target configurations, missing metadata, and native limited-API wheel builds, including --skip-build and importing the extension. Add Python 3.15t to CI. --- .github/workflows/main.yml | 7 +- newsfragments/5360.bugfix.rst | 1 + setuptools/command/bdist_wheel.py | 88 ++++++--- setuptools/tests/test_bdist_wheel.py | 277 ++++++++++++++++++++++++--- 4 files changed, 321 insertions(+), 52 deletions(-) create mode 100644 newsfragments/5360.bugfix.rst diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8a7997cd54..51d5eb8e7f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,11 +30,14 @@ jobs: python: - "3.10" - "3.14" - - "3.14t" + - "3.15t" platform: - ubuntu-latest - macos-latest - windows-latest + exclude: + - python: "3.15t" # PyNaCl lacks Windows 3.15t wheels. + platform: windows-latest include: - python: "3.11" platform: ubuntu-latest @@ -45,6 +48,8 @@ jobs: # Disabled: cffi (required by PyNaCl) doesn't support free-threaded CPython 3.13t. # - python: "3.13t" # platform: ubuntu-latest + - python: "3.14t" + platform: windows-latest - python: "3.15" platform: ubuntu-latest - python: pypy3.11 diff --git a/newsfragments/5360.bugfix.rst b/newsfragments/5360.bugfix.rst new file mode 100644 index 0000000000..09b333d1f1 --- /dev/null +++ b/newsfragments/5360.bugfix.rst @@ -0,0 +1 @@ +Changed wheel tags and stable-ABI validation to use sysconfig data. diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index a1cde932e0..298fc40dbd 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -16,7 +16,6 @@ from collections.abc import Iterable, Sequence from email.generator import BytesGenerator from glob import iglob -from itertools import chain from typing import ClassVar, Literal, cast from zipfile import ZIP_DEFLATED, ZIP_STORED @@ -31,8 +30,6 @@ from distutils import log -flatten = chain.from_iterable - def safe_version(version: str) -> str: """ @@ -51,12 +48,45 @@ def safe_version(version: str) -> str: PY_LIMITED_API_PATTERN = r"cp3\d" +def _interpreter_name() -> str: + soabi = sysconfig.get_config_var("SOABI") or "" + if re.match(r"(?:cpython-|cp)\d", soabi): + return "cp" + if soabi.startswith("pypy"): + return "pp" + if soabi.startswith("graalpy"): + return "graalpy" + return tags.interpreter_name() + + +def _python_version() -> tuple[int, int]: + # VERSION comes from the target configuration; py_version_short does not. + version = sysconfig.get_config_var("VERSION") + # Windows uses e.g. '315', whereas POSIX uses '3.15'. + match = re.fullmatch(r"(\d)\.?([0-9]+)", str(version)) + if match is None: + soabi = str(sysconfig.get_config_var("SOABI")) + match = re.match(r"(?:cpython-|cp|pypy|graalpy[^-]*-)(\d)(\d+)", soabi) + return (int(match[1]), int(match[2])) if match else sys.version_info[:2] + + def _is_32bit_interpreter() -> bool: + size = sysconfig.get_config_var("SIZEOF_VOID_P") + if size is not None: + return size == 4 return struct.calcsize("P") == 4 +def _is_debug(*, warn: bool = False) -> bool: + fallback = hasattr(sys, "gettotalrefcount") + suffix = sysconfig.get_config_var("EXT_SUFFIX") + if isinstance(suffix, str) and suffix.endswith(".pyd"): + fallback = suffix.startswith("_d") + return get_flag("Py_DEBUG", fallback, warn=warn) + + def python_tag() -> str: - return f"py{sys.version_info.major}" + return f"py{_python_version()[0]}" def get_platform(archive_root: str | None) -> str: @@ -99,21 +129,22 @@ def get_flag( def get_abi_tag() -> str | None: """Return the ABI tag based on SOABI (if available) or emulate SOABI (PyPy2).""" soabi: str = sysconfig.get_config_var("SOABI") - impl = tags.interpreter_name() + impl = _interpreter_name() if not soabi and impl in ("cp", "pp") and hasattr(sys, "maxunicode"): d = "" u = "" - if get_flag("Py_DEBUG", hasattr(sys, "gettotalrefcount"), warn=(impl == "cp")): + if _is_debug(warn=(impl == "cp")): d = "d" - abi = f"{impl}{tags.interpreter_version()}{d}{u}" + impl_ver = "".join(map(str, _python_version())) + abi = f"{impl}{impl_ver}{d}{u}" elif soabi and impl == "cp" and soabi.startswith("cpython"): # non-Windows abi = "cp" + soabi.split("-")[1] elif soabi and impl == "cp" and soabi.startswith("cp"): # Windows abi = soabi.split("-")[0] - if hasattr(sys, "gettotalrefcount"): + if _is_debug() and not abi.endswith("d"): # using debug build; append "d" flag abi += "d" elif soabi and impl == "pp": @@ -311,7 +342,7 @@ def _validate_py_limited_api(self) -> None: if not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'") - if sysconfig.get_config_var("Py_GIL_DISABLED") and sys.version_info < (3, 15): + if sysconfig.get_config_var("Py_GIL_DISABLED") and _python_version() < (3, 15): raise ValueError( f"`py_limited_api={self.py_limited_api!r}` not supported. " "`Py_LIMITED_API` is incompatible with `Py_GIL_DISABLED` " @@ -332,8 +363,8 @@ def wheel_dist_name(self) -> str: @property def abi_tag(self) -> str: - impl_name = tags.interpreter_name() - impl_ver = tags.interpreter_version() + impl_name = _interpreter_name() + impl_ver = "".join(map(str, _python_version())) tag = None if self.py_limited_api: tag = stable_abi_tag(impl_name, impl_ver) @@ -381,25 +412,36 @@ def get_tag(self) -> tuple[str, str, str]: impl = self.python_tag tag = (impl, "none", plat_name) else: - impl_name = tags.interpreter_name() - impl_ver = tags.interpreter_version() + impl_name = _interpreter_name() + python_version = _python_version() + impl_ver = "".join(map(str, python_version)) impl = impl_name + impl_ver abi_tag = self.abi_tag if "abi3" in abi_tag: assert self.py_limited_api is not False impl = self.py_limited_api tag = (impl, abi_tag, plat_name) - possible_tags = tags.parse_tag("-".join(tag)) - # issue gh-374: allow overriding plat_name - sys_tags = (f"{t.interpreter}-{t.abi}-{plat_name}" for t in tags.sys_tags()) - supported_tags = list(flatten(tags.parse_tag(t) for t in sys_tags)) - # abi_tag can contain multiple (e.g. "abi3.abi3t") tags - # only one of them will be supported - assert any(t in supported_tags for t in possible_tags), ( - f"would build wheel with unsupported tag {tag}" - ) + self._validate_tag(tag) return tag + def _validate_tag(self, tag: tuple[str, str, str]) -> None: + impl_name = _interpreter_name() + python_version = _python_version() + abis = [str(get_abi_tag()).lower()] + # issue gh-374: allow overriding plat_name + if impl_name == "cp": + supported_tags = tags.cpython_tags( + python_version=python_version, abis=abis, platforms=[tag[2]] + ) + else: + supported_tags = tags.generic_tags( + interpreter=tag[0], abis=abis, platforms=[tag[2]] + ) + # A compressed ABI tag need only match one of the target's supported tags. + assert not tags.parse_tag("-".join(tag)).isdisjoint(supported_tags), ( + f"would build wheel with unsupported tag {tag}" + ) + def run(self): build_scripts = self.reinitialize_command("build_scripts") build_scripts.executable = "python" @@ -485,7 +527,7 @@ def run(self): # Add to 'Distribution.dist_files' so that the "upload" command works getattr(self.distribution, "dist_files", []).append(( "bdist_wheel", - f"{sys.version_info.major}.{sys.version_info.minor}", + ".".join(map(str, _python_version())), wheel_path, )) diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index ddff340e05..4b7a708488 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -20,12 +20,22 @@ import setuptools from setuptools.archive_util import unpack_zipfile -from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag, stable_abi_tag +from setuptools.command.bdist_wheel import ( + _interpreter_name, + _is_32bit_interpreter, + _is_debug, + _python_version, + bdist_wheel, + get_abi_tag, + get_platform, + stable_abi_tag, +) from setuptools.command.build_ext import get_abi3_suffix from setuptools.dist import Distribution from setuptools.warnings import SetuptoolsDeprecationWarning from distutils.core import run_setup +from distutils.sysconfig import get_config_vars as get_distutils_config_vars DEFAULT_FILES = { "dummy_dist-1.0.dist-info/top_level.txt", @@ -266,6 +276,15 @@ def licenses_dist(tmp_path_factory): return mkexample(tmp_path_factory, "licenses-dist") +@pytest.mark.parametrize('version', ['3.14', '3.15']) +def test_dist_files_target_version(dummy_dist, monkeypatch, version): + monkeypatch.chdir(dummy_dist) + monkeypatch.setitem(sysconfig.get_config_vars(), 'VERSION', version) + cmd = bdist_wheel_cmd() + cmd.run() + assert cmd.distribution.dist_files[0][1] == version + + def test_no_scripts(wheel_paths): """Make sure entry point scripts are not generated.""" path = next(path for path in wheel_paths if "complex_dist" in path) @@ -511,7 +530,8 @@ def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path): @pytest.mark.filterwarnings( "once:Config variable '.*' is unset.*, Python ABI tag may be incorrect" ) -def test_limited_api(monkeypatch, tmp_path, tmp_path_factory): +@pytest.mark.parametrize('skip_build', [False, True]) +def test_limited_api(monkeypatch, tmp_path, tmp_path_factory, skip_build): """Test that building a binary wheel with the limited ABI works.""" source_dir = tmp_path_factory.mktemp("extension_dist") (source_dir / "setup.py").write_text(EXTENSION_SETUPPY, encoding="utf-8") @@ -523,9 +543,42 @@ def test_limited_api(monkeypatch, tmp_path, tmp_path_factory): ft_unsupported = IS_FREE_THREADED and sys.version_info < (3, 15) expectation = pytest.raises(ValueError) if ft_unsupported else nullcontext() with expectation: - bdist_wheel_cmd( - bdist_dir=str(build_dir), dist_dir=str(dist_dir), py_limited_api="cp32" - ).run() + cmd = bdist_wheel_cmd( + bdist_dir=str(build_dir), + dist_dir=str(dist_dir), + py_limited_api="cp32", + skip_build=skip_build, + ) + if skip_build: + cmd.run_command('build_ext') + cmd.run() + + if ft_unsupported: # pragma: no cover + return + + abi = get_abi_tag() + if tags.interpreter_name() == 'cp': + abi = 'abi3.abi3t' if IS_FREE_THREADED else 'abi3' + wheel = next(dist_dir.glob('*.whl')) + assert f'-{abi}-' in wheel.name + installed = tmp_path / 'installed' + with ZipFile(wheel) as archive: + suffix = get_abi3_suffix() or sysconfig.get_config_var('EXT_SUFFIX') + assert 'extension' + suffix in archive.namelist() + metadata = archive.read('extension_dist-0.1.dist-info/WHEEL').decode() + for tag in tags.parse_tag('-'.join(wheel.stem.split('-')[-3:])): + assert f'Tag: {tag}\n' in metadata + # Cygwin requires executable permissions when loading DLLs. + unpack_zipfile(wheel, installed) + subprocess.run( + [ + sys.executable, + '-c', + 'import extension; assert extension.__doc__ == "Dummy extension module"', + ], + cwd=installed, + check=True, + ) @pytest.mark.filterwarnings( @@ -553,7 +606,7 @@ def test_version_specific_abi(monkeypatch, tmp_path, tmp_path_factory, limited_a @pytest.mark.skipif( 'abi3extension-dist' not in EXAMPLES or not get_abi3_suffix(), - reason='requires a GIL-enabled build with stable-ABI suffix support', + reason='requires stable-ABI suffix support', ) @pytest.mark.parametrize('skip_build', [False, True]) def test_abi3_filename_consistent(monkeypatch, tmp_path, tmp_path_factory, skip_build): @@ -563,6 +616,7 @@ def test_abi3_filename_consistent(monkeypatch, tmp_path, tmp_path_factory, skip_ monkeypatch.chdir(mkexample(tmp_path_factory, 'abi3extension-dist')) monkeypatch.setitem(get_config_vars(), 'SOABI_PLATFORM', 'target-platform') ext = os.path.splitext(get_config_vars()['EXT_SUFFIX'])[1] + abi = 'abi3t' if IS_FREE_THREADED else 'abi3' cmd = bdist_wheel_cmd( py_limited_api='cp32', dist_dir=str(tmp_path), skip_build=skip_build ) @@ -574,7 +628,7 @@ def test_abi3_filename_consistent(monkeypatch, tmp_path, tmp_path_factory, skip_ assert build_ext.get_outputs() == outputs wheel = next(tmp_path.glob('*.whl')) with ZipFile(wheel) as archive: - assert f'extension.abi3-target-platform{ext}' in archive.namelist() + assert f'extension.{abi}-target-platform{ext}' in archive.namelist() def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path): @@ -623,16 +677,39 @@ def test_unix_epoch_timestamps(dummy_dist, monkeypatch, tmp_path): assert zinfo.date_time >= (1980, 1, 1, 0, 0, 0) # min epoch is used -def test_get_abi_tag_windows(monkeypatch): - monkeypatch.setattr(tags, "interpreter_name", lambda: "cp") - monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313-win_amd64") - assert get_abi_tag() == "cp313" +@pytest.mark.parametrize('free_threaded', [False, True]) +@pytest.mark.parametrize('debug', [False, True]) +@pytest.mark.parametrize('debug_var', [False, True]) +def test_get_abi_tag_windows(monkeypatch, free_threaded, debug, debug_var): + abi = 'cp313t' if free_threaded else 'cp313' + config = { + 'SOABI': f'{abi}-win_amd64', + 'EXT_SUFFIX': f'{"_d" if debug else ""}.{abi}-win_amd64.pyd', + 'Py_DEBUG': int(debug) if debug_var else None, + } + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) monkeypatch.setattr(sys, "gettotalrefcount", lambda: 1, False) - assert get_abi_tag() == "cp313d" - monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313t-win_amd64") - assert get_abi_tag() == "cp313td" + expected = abi + ('d' if debug else '') + assert get_abi_tag() == expected monkeypatch.delattr(sys, "gettotalrefcount") - assert get_abi_tag() == "cp313t" + assert get_abi_tag() == expected + + +@pytest.mark.parametrize('debug', [False, True]) +def test_get_abi_tag_windows_without_soabi(monkeypatch, debug): + config = {'VERSION': '312', 'EXT_SUFFIX': '_d.pyd' if debug else '.pyd'} + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) + monkeypatch.setattr(tags, 'interpreter_name', lambda: 'cp') + monkeypatch.setattr(sys, 'gettotalrefcount', lambda: 1, raising=False) + with pytest.warns(RuntimeWarning, match="Config variable 'Py_DEBUG' is unset"): + assert get_abi_tag() == ('cp312d' if debug else 'cp312') + + +@pytest.mark.parametrize('debug', [False, True]) +def test_is_debug_from_sysconfig(monkeypatch, debug): + config = {'Py_DEBUG': int(debug), 'EXT_SUFFIX': '.pyd' if debug else '_d.pyd'} + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) + assert _is_debug() is debug def test_get_abi_tag_pypy_old(monkeypatch): @@ -668,19 +745,160 @@ def test_stable_abi_tag(monkeypatch): assert stable_abi_tag("cp", "315") == "abi3.abi3t" -def test_py_limited_api_free_threaded_before_315(monkeypatch): - """py_limited_api is rejected on free-threaded Pythons older than 3.15.""" - cmd = bdist_wheel(Distribution()) - cmd.py_limited_api = "cp32" - get_config_var = sysconfig.get_config_var - monkeypatch.setattr( - sysconfig, - "get_config_var", - lambda name: 1 if name == "Py_GIL_DISABLED" else get_config_var(name), +@pytest.mark.parametrize('host_version', [(3, 14), (3, 15)]) +@pytest.mark.parametrize('target_version', [(3, 14), (3, 15)]) +@pytest.mark.parametrize('version_available', [False, True]) +@pytest.mark.parametrize('free_threaded', [False, True]) +@pytest.mark.parametrize('limited_api', [False, 'cp32']) +@pytest.mark.parametrize(('arch', 'pointer_size'), [('aarch64', 8), ('i686', 4)]) +def test_cross_tags( + monkeypatch, + host_version, + target_version, + version_available, + free_threaded, + limited_api, + arch, + pointer_size, +): + """Wheel tags and extension filenames describe the target, not the host.""" + flags = 't' if free_threaded else '' + version = ''.join(map(str, target_version)) + soabi = f'cpython-{version}{flags}-{arch}-linux-gnu' + soabi_platform = f'{arch}-linux-gnu' if target_version >= (3, 15) else None + config = { + 'VERSION': '.'.join(map(str, target_version)) if version_available else None, + 'SOABI': soabi, + 'SOABI_PLATFORM': soabi_platform, + 'EXT_SUFFIX': f'.{soabi}.so', + 'Py_GIL_DISABLED': int(free_threaded), + 'Py_DEBUG': 0, + 'SIZEOF_VOID_P': pointer_size, + 'HAVE_DYNAMIC_LOADING': 1, + 'DYNLOADFILE': 'dynload_shlib.o', + } + for name, value in config.items(): + monkeypatch.setitem(sysconfig.get_config_vars(), name, value) + monkeypatch.setitem(get_distutils_config_vars(), name, value) + extension = setuptools.Extension('extension', ['extension.c'], py_limited_api=True) + cmd = bdist_wheel(Distribution({'ext_modules': [extension]})) + cmd.root_is_pure = False + cmd.plat_name = f'linux-{arch}' + cmd.py_limited_api = limited_api + build_ext = cmd.get_finalized_command('build_ext') + monkeypatch.setattr(sys, 'version_info', (*host_version, 0, 'final', 0)) + monkeypatch.setattr(struct, 'calcsize', lambda _: 12 - pointer_size) + monkeypatch.setattr(tags, 'interpreter_name', lambda: 'pp') + monkeypatch.setattr(tags, 'interpreter_version', lambda: '999') + + def unexpected_sys_tags(): # pragma: no cover + pytest.fail('tags.sys_tags() describes the host, not the target') + + monkeypatch.setattr(tags, 'sys_tags', unexpected_sys_tags) + if limited_api and free_threaded and target_version < (3, 15): + with pytest.raises(ValueError, match='not supported'): + cmd._validate_py_limited_api() + return + + cmd._validate_py_limited_api() + impl = limited_api or f'cp{version}' + abi = ( + ('abi3.abi3t' if free_threaded else 'abi3') + if limited_api + else f'cp{version}{flags}' + ) + assert cmd.get_tag() == (impl, abi, f'linux_{arch}') + qualifier = f'-{soabi_platform}' if soabi_platform else '' + assert build_ext.get_ext_filename('extension') == ( + f"extension{config['EXT_SUFFIX']}" + if free_threaded and target_version < (3, 15) + else f'extension.abi3{flags}{qualifier}.so' ) - monkeypatch.setattr(sys, "version_info", (3, 14, 5)) - with pytest.raises(ValueError, match="not supported"): - cmd._validate_py_limited_api() + + +@pytest.mark.parametrize('free_threaded', [False, True]) +def test_limited_api_newer_than_target(monkeypatch, free_threaded): + cmd = bdist_wheel(Distribution()) + cmd.root_is_pure = False + cmd.plat_name = 'linux_aarch64' + cmd.py_limited_api = 'cp316' + config = { + 'VERSION': '3.15', + 'SOABI': f'cpython-315{"t" if free_threaded else ""}-aarch64-linux-gnu', + 'Py_GIL_DISABLED': int(free_threaded), + 'SIZEOF_VOID_P': 8, + } + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) + with pytest.raises(AssertionError, match='unsupported tag'): + cmd.get_tag() + + +@pytest.mark.parametrize( + ('version', 'soabi', 'expected'), + [ + ('3.14', 'cpython-315-aarch64-linux-gnu', (3, 14)), + ('3.15', None, (3, 15)), + ('315', None, (3, 15)), + (315, None, (3, 15)), + (None, 'cpython-314-aarch64-linux-gnu', (3, 14)), + ('invalid', 'cpython-314td-x86_64-linux-gnu', (3, 14)), + (None, 'cp314-win_amd64', (3, 14)), + (None, 'cp315t-win_arm64', (3, 15)), + (None, 'pypy311-pp80', (3, 11)), + (None, 'graalpy-38-native-x86_64-darwin', (3, 8)), + (None, 'graalpy231-310-native-x86_64-linux', (3, 10)), + (None, None, sys.version_info[:2]), + ('invalid', 'unknown', sys.version_info[:2]), + (None, 'cpython-3', sys.version_info[:2]), + (None, 'graalpy231-', sys.version_info[:2]), + ], +) +def test_target_python_version(monkeypatch, version, soabi, expected): + config = {'VERSION': version, 'SOABI': soabi} + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) + assert _python_version() == expected + + +@pytest.mark.parametrize( + ('helper', 'expected'), + [ + (_python_version, sys.version_info[:2]), + (_interpreter_name, tags.interpreter_name()), + (_is_32bit_interpreter, struct.calcsize('P') == 4), + (_is_debug, hasattr(sys, 'gettotalrefcount')), + ], +) +@pytest.mark.parametrize( + 'override', + [ + None, + '_PYTHON_SYSCONFIGDATA_NAME', + '_PYTHON_SYSCONFIGDATA_PATH', + '_PYTHON_HOST_PLATFORM', + ], +) +def test_config_fallback(monkeypatch, helper, expected, override): + monkeypatch.setattr(sysconfig, 'get_config_var', lambda _: None) + if override: + monkeypatch.setenv(override, 'override') + assert helper() == expected + + +@pytest.mark.parametrize( + ('soabi', 'implementation', 'abi'), + [ + ('pypy311-pp73-aarch64-linux-gnu', 'pp', 'pypy311_pp73'), + ('graalpy231-311-native-aarch64-linux', 'graalpy', 'graalpy231_311_native'), + ], +) +def test_non_cpython_target(monkeypatch, soabi, implementation, abi): + cmd = bdist_wheel(Distribution()) + cmd.root_is_pure = False + cmd.plat_name = 'linux_aarch64' + config = {'VERSION': '3.11', 'SOABI': soabi, 'SIZEOF_VOID_P': 8} + monkeypatch.setattr(sysconfig, 'get_config_var', config.get) + monkeypatch.setattr(tags, 'interpreter_name', lambda: 'cp') + assert cmd.get_tag() == (f'{implementation}311', abi, 'linux_aarch64') def test_platform_with_space(dummy_dist, monkeypatch): @@ -752,7 +970,10 @@ def test_data_dir_with_tag_build(monkeypatch, tmp_path): platform.system() != "Linux", reason="Only makes sense to test on Linux" ) def test_platform_linux32(reported, expected, monkeypatch): - monkeypatch.setattr(struct, "calcsize", lambda x: 4) + monkeypatch.setitem(sysconfig.get_config_vars(), 'SIZEOF_VOID_P', 4) + monkeypatch.setattr(struct, "calcsize", lambda x: 8) + monkeypatch.setattr(sysconfig, 'get_platform', lambda: reported) + assert get_platform(None) == expected dist = setuptools.Distribution() cmd = bdist_wheel(dist) cmd.plat_name = reported