diff --git a/setup.py b/setup.py index 89cde3d..fa6bfeb 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,20 @@ #!/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: + # 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 = 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 +55,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 0000000..6964422 --- /dev/null +++ b/tests/test_setup.py @@ -0,0 +1,96 @@ +"""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 + + +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 = {} + # 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) + 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'), + ('win32', 'ARM64', '32bit', 'libsndfile_x86.dll', 'win32'), +]) +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