From fbb8f7c769aa1d2442041b487619e7918f950b4c Mon Sep 17 00:00:00 2001 From: CAOShurong <170531907+CAOShurong@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:08:40 +0800 Subject: [PATCH 1/2] Fix Windows wheel architecture detection --- setup.py | 14 ++++--- tests/test_setup.py | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 tests/test_setup.py diff --git a/setup.py b/setup.py index 89cde3d7..38b8374b 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,19 @@ #!/usr/bin/env python import os -from platform import architecture, machine -from setuptools import setup import sys +import sysconfig +from platform import machine + +from setuptools import setup # environment variables for cross-platform package creation platform = os.environ.get('PYSOUNDFILE_PLATFORM', sys.platform) architecture0 = os.environ.get('PYSOUNDFILE_ARCHITECTURE') if architecture0 is None: - # follow the same decision tree as in soundfile.py after - # _find_library('sndfile') fails: + # Follow the packaged library selection in soundfile.py: if sys.platform == 'win32': - architecture0 = architecture()[0] # 64bit or 32bit + architecture0 = {'win32': 'x86', 'win-amd64': 'x64', + 'win-arm64': 'arm64'}[sysconfig.get_platform()] else: architecture0 = machine() # x86_64 or arm64 @@ -52,7 +54,7 @@ def get_tag(self): else: oses = 'macosx_11_0_arm64' elif platform == 'win32': - if architecture0.lower() == 'arm64' or machine() == 'ARM64': + if architecture0.lower() == 'arm64': oses = 'win_arm64' elif architecture0 == 'x86' or architecture0 == '32bit': oses = 'win32' diff --git a/tests/test_setup.py b/tests/test_setup.py new file mode 100644 index 00000000..6bc1d826 --- /dev/null +++ b/tests/test_setup.py @@ -0,0 +1,93 @@ +"""Keep wheel platform tags and bundled library selection consistent.""" + +import os +import platform +import runpy +import sys +import sysconfig +from pathlib import Path +from types import ModuleType + +import pytest +import setuptools + + +def setup_options(monkeypatch, host, python_platform, machine, bits, + target=None, architecture=None, packaged=True): + root = Path(__file__).resolve().parents[1] + monkeypatch.chdir(root) + monkeypatch.setattr(sys, 'platform', host) + monkeypatch.setattr(sysconfig, 'get_platform', lambda: python_platform) + monkeypatch.setattr(platform, 'machine', lambda: machine) + monkeypatch.setattr(platform, 'architecture', lambda: (bits, '')) + for name, value in [('PYSOUNDFILE_PLATFORM', target), + ('PYSOUNDFILE_ARCHITECTURE', architecture)]: + if value is None: + monkeypatch.delenv(name, raising=False) + else: + monkeypatch.setenv(name, value) + original_isdir = os.path.isdir + monkeypatch.setattr(os.path, 'isdir', lambda path: + packaged if path == '_soundfile_data' else original_isdir(path)) + options = {} + monkeypatch.setattr(setuptools, 'setup', lambda **kwargs: options.update(kwargs)) + wheel_command = ModuleType('wheel.bdist_wheel') + wheel_command.bdist_wheel = object + monkeypatch.setitem(sys.modules, 'wheel.bdist_wheel', wheel_command) + namespace = runpy.run_path(str(root / 'setup.py')) + return namespace, options + + +@pytest.mark.parametrize('python_platform,machine,bits,library,tag', [ + ('win32', 'AMD64', '32bit', 'libsndfile_x86.dll', 'win32'), + ('win-amd64', 'AMD64', '64bit', 'libsndfile_x64.dll', 'win_amd64'), + ('win-arm64', 'ARM64', '64bit', 'libsndfile_arm64.dll', 'win_arm64'), + ('win-amd64', 'ARM64', '64bit', 'libsndfile_x64.dll', 'win_amd64'), +]) +def test_default_windows_architecture(monkeypatch, python_platform, machine, + bits, library, tag): + namespace, options = setup_options(monkeypatch, 'win32', python_platform, + machine, bits) + assert namespace['libname'] == library + assert options['package_data']['_soundfile_data'] == [library, 'COPYING'] + assert options['zip_safe'] is False + assert options['cmdclass']['bdist_wheel'].get_tag(None) == ('py2.py3', 'none', tag) + + +@pytest.mark.parametrize('target,architecture,machine,library,tag', [ + ('win32', 'x64', 'ARM64', 'libsndfile_x64.dll', 'win_amd64'), + ('win32', 'x86', 'ARM64', 'libsndfile_x86.dll', 'win32'), + ('win32', 'arm64', 'AMD64', 'libsndfile_arm64.dll', 'win_arm64'), + ('win32', '64bit', 'AMD64', 'libsndfile_64bit.dll', 'win_amd64'), + ('win32', '32bit', 'AMD64', 'libsndfile_32bit.dll', 'win32'), + ('darwin', 'x86_64', 'AMD64', 'libsndfile_x86_64.dylib', 'macosx_10_9_x86_64'), + ('darwin', 'arm64', 'AMD64', 'libsndfile_arm64.dylib', 'macosx_11_0_arm64'), + ('linux', 'x86_64', 'AMD64', 'libsndfile_x86_64.so', 'manylinux_2_28_x86_64'), + ('linux', 'arm64', 'AMD64', 'libsndfile_arm64.so', 'manylinux_2_28_aarch64'), +]) +def test_explicit_target(monkeypatch, target, architecture, machine, library, tag): + namespace, options = setup_options(monkeypatch, 'win32', 'win-amd64', + machine, '64bit', target, architecture) + assert namespace['architecture0'] == architecture + assert namespace['libname'] == library + assert options['package_data']['_soundfile_data'] == [library, 'COPYING'] + assert options['cmdclass']['bdist_wheel'].get_tag(None) == ('py2.py3', 'none', tag) + + +@pytest.mark.parametrize('host,machine,library', [ + ('darwin', 'x86_64', 'libsndfile_x86_64.dylib'), + ('darwin', 'arm64', 'libsndfile_arm64.dylib'), + ('linux', 'x86_64', 'libsndfile_x86_64.so'), +]) +def test_default_non_windows_architecture(monkeypatch, host, machine, library): + namespace, _ = setup_options(monkeypatch, host, host, machine, '64bit') + assert namespace['libname'] == library + + +@pytest.mark.parametrize('target,packaged', [('noplatform', True), ('win32', False)]) +def test_without_packaged_library(monkeypatch, target, packaged): + _, options = setup_options(monkeypatch, 'win32', 'win-amd64', 'AMD64', + '64bit', target, 'x64', packaged) + assert options['packages'] is None + assert options['package_data'] is None + assert options['zip_safe'] is True From ed39de355ad0f6ffc7bb262df7fb4ce14d3559d6 Mon Sep 17 00:00:00 2001 From: CAOShurong <170531907+CAOShurong@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:54:36 +0800 Subject: [PATCH 2/2] test: run packaging regressions without build tools installed --- setup.py | 3 ++- tests/test_setup.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 38b8374b..fa6bfeb6 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ platform = os.environ.get('PYSOUNDFILE_PLATFORM', sys.platform) architecture0 = os.environ.get('PYSOUNDFILE_ARCHITECTURE') if architecture0 is None: - # Follow the packaged library selection in soundfile.py: + # Match soundfile.py using the interpreter's architecture. The host machine + # can be ARM64 even when running an x64 or x86 Python under emulation. if sys.platform == 'win32': architecture0 = {'win32': 'x86', 'win-amd64': 'x64', 'win-arm64': 'arm64'}[sysconfig.get_platform()] diff --git a/tests/test_setup.py b/tests/test_setup.py index 6bc1d826..69644225 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -9,7 +9,6 @@ from types import ModuleType import pytest -import setuptools def setup_options(monkeypatch, host, python_platform, machine, bits, @@ -30,7 +29,10 @@ def setup_options(monkeypatch, host, python_platform, machine, bits, monkeypatch.setattr(os.path, 'isdir', lambda path: packaged if path == '_soundfile_data' else original_isdir(path)) options = {} - monkeypatch.setattr(setuptools, 'setup', lambda **kwargs: options.update(kwargs)) + # Packaging tools are build-time dependencies, absent from a normal install. + setuptools = ModuleType('setuptools') + setuptools.setup = lambda **kwargs: options.update(kwargs) + monkeypatch.setitem(sys.modules, 'setuptools', setuptools) wheel_command = ModuleType('wheel.bdist_wheel') wheel_command.bdist_wheel = object monkeypatch.setitem(sys.modules, 'wheel.bdist_wheel', wheel_command) @@ -43,6 +45,7 @@ def setup_options(monkeypatch, host, python_platform, machine, bits, ('win-amd64', 'AMD64', '64bit', 'libsndfile_x64.dll', 'win_amd64'), ('win-arm64', 'ARM64', '64bit', 'libsndfile_arm64.dll', 'win_arm64'), ('win-amd64', 'ARM64', '64bit', 'libsndfile_x64.dll', 'win_amd64'), + ('win32', 'ARM64', '32bit', 'libsndfile_x86.dll', 'win32'), ]) def test_default_windows_architecture(monkeypatch, python_platform, machine, bits, library, tag):