Implement testing for universal2/arm64 wheels

This commit is contained in:
Joe Rickerby
2021-01-05 15:58:53 +00:00
parent df60457001
commit 577fd5e8e3
+60 -9
View File
@@ -7,7 +7,7 @@ import sys
import tempfile import tempfile
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Sequence, Tuple, cast from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, cast
from .environment import ParsedEnvironment from .environment import ParsedEnvironment
from .logger import log from .logger import log
@@ -330,16 +330,67 @@ def build(options: BuildOptions) -> None:
repaired_wheel.rename(renamed_wheel) repaired_wheel.rename(renamed_wheel)
repaired_wheel = renamed_wheel repaired_wheel = renamed_wheel
if options.test_command: log.step_end()
log.step('Testing wheel...')
machine_arch = platform.machine()
testing_archs: List[str] = []
if machine_arch == 'x86_64':
if config.identifier.endswith('_arm64'):
log.warning(wrap_text('''
While arm64 wheels can be built on x86_64, they cannot be tested. The
ability to test the arm64 wheels will be added in a future release of
cibuildwheel, once Apple Silicon CI runners are widely available.
'''))
testing_archs = []
elif config.identifier.endswith('_universal2'):
log.warning(wrap_text('''
While universal2 wheels can be built on x86_64, the arm64 part of them
cannot currently be tested. The ability to test the arm64 part of a
universal2 wheel will be added in a future release of cibuildwheel, once
Apple Silicon CI runners are widely available.
'''))
testing_archs = ['x86_64']
else:
testing_archs = ['x86_64']
elif machine_arch == 'arm64':
if config.identifier.endswith('_x86_64'):
# testing using rosetta2 emulation
testing_archs = ['x86_64']
elif config.identifier.endswith('_universal2'):
# testing the x86_64 using rosetta2 emulation
testing_archs = ['arm64', 'x86_64']
else:
testing_archs = ['arm64']
if options.test_command and len(testing_archs) > 0:
for testing_arch in testing_archs:
log.step('Testing wheel...' if testing_arch == machine_arch else f'Testing wheel on {testing_arch}...')
# set up a virtual environment to install and test from, to make sure # set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time. # there are no dependencies that were pulled in at build time.
call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env) call(['pip', 'install', 'virtualenv', *dependency_constraint_flags], env=env)
venv_dir = Path(tempfile.mkdtemp()) venv_dir = Path(tempfile.mkdtemp())
arch_prefix = []
if testing_arch != machine_arch:
if machine_arch == 'arm64' and testing_arch == 'x86_64':
# rosetta2 will provide the emulation with just the arch prefix.
arch_prefix = ['arch', '-x86_64']
else:
raise RuntimeError("don't know how to emulate {testing_arch} on {machine_arch}")
# define a custom 'call' function that adds the arch prefix each time
def call_with_arch(args: Sequence[PathOrStr], **kwargs: Any) -> int:
if isinstance(args, str):
args = ' '.join(arch_prefix) + ' ' + args
else:
args = [*arch_prefix, *args]
return call(args, **kwargs)
# Use --no-download to ensure determinism by using seed libraries # Use --no-download to ensure determinism by using seed libraries
# built into virtualenv # built into virtualenv
call(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env) call_with_arch(['python', '-m', 'virtualenv', '--no-download', venv_dir], env=env)
virtualenv_env = env.copy() virtualenv_env = env.copy()
virtualenv_env['PATH'] = os.pathsep.join([ virtualenv_env['PATH'] = os.pathsep.join([
@@ -348,18 +399,18 @@ def build(options: BuildOptions) -> None:
]) ])
# check that we are using the Python from the virtual environment # check that we are using the Python from the virtual environment
call(['which', 'python'], env=virtualenv_env) call_with_arch(['which', 'python'], env=virtualenv_env)
if options.before_test: if options.before_test:
before_test_prepared = prepare_command(options.before_test, project='.', package=options.package_dir) before_test_prepared = prepare_command(options.before_test, project='.', package=options.package_dir)
call(before_test_prepared, env=virtualenv_env, shell=True) call_with_arch(before_test_prepared, env=virtualenv_env, shell=True)
# install the wheel # install the wheel
call(['pip', 'install', str(repaired_wheel) + options.test_extras], env=virtualenv_env) call_with_arch(['pip', 'install', str(repaired_wheel) + options.test_extras], env=virtualenv_env)
# test the wheel # test the wheel
if options.test_requires: if options.test_requires:
call(['pip', 'install'] + options.test_requires, env=virtualenv_env) call_with_arch(['pip', 'install'] + options.test_requires, env=virtualenv_env)
# run the tests from $HOME, with an absolute path in the command # run the tests from $HOME, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel # (this ensures that Python runs the tests against the installed wheel
@@ -369,7 +420,7 @@ def build(options: BuildOptions) -> None:
project=Path('.').resolve(), project=Path('.').resolve(),
package=options.package_dir.resolve() package=options.package_dir.resolve()
) )
call(test_command_prepared, cwd=os.environ['HOME'], env=virtualenv_env, shell=True) call_with_arch(test_command_prepared, cwd=os.environ['HOME'], env=virtualenv_env, shell=True)
# clean up # clean up
shutil.rmtree(venv_dir) shutil.rmtree(venv_dir)