2022-07-14 13:36:57 +02:00
from __future__ import annotations
2022-02-27 14:16:37 -05:00
import functools
2019-11-12 23:51:27 +00:00
import os
2021-01-05 10:23:36 +00:00
import platform
2021-01-30 17:15:44 +00:00
import re
2019-11-12 23:51:27 +00:00
import shutil
import subprocess
2020-02-19 17:56:11 +01:00
import sys
2022-04-28 09:19:27 -04:00
from dataclasses import dataclass
2020-06-15 01:53:31 +02:00
from pathlib import Path
2022-07-14 13:36:57 +02:00
from typing import Sequence , Tuple , cast
2020-04-08 00:16:25 +02:00
2022-01-08 11:30:48 +01:00
from filelock import FileLock
2021-01-23 13:28:06 +00:00
from .architecture import Architecture
2020-04-10 01:44:02 +02:00
from .environment import ParsedEnvironment
2020-11-13 16:30:27 +00:00
from .logger import log
2021-10-12 02:05:47 +01:00
from .options import Options
2021-06-23 10:47:18 -04:00
from .typing import Literal , PathOrStr , assert_never
2021-01-06 13:50:58 -05:00
from .util import (
2022-01-08 11:30:48 +01:00
CIBW_CACHE_PATH ,
2022-06-19 18:03:05 +02:00
AlreadyBuiltWheelError ,
2021-06-23 10:47:18 -04:00
BuildFrontend ,
2021-01-06 13:50:58 -05:00
BuildSelector ,
NonPlatformWheelError ,
2022-01-05 02:59:24 +01:00
call ,
2022-01-08 11:30:48 +01:00
detect_ci_provider ,
2021-01-06 13:50:58 -05:00
download ,
2022-06-19 16:34:28 +02:00
find_compatible_wheel ,
2021-01-06 13:50:58 -05:00
get_build_verbosity_extra_flags ,
2021-06-23 10:47:18 -04:00
get_pip_version ,
2021-01-06 13:50:58 -05:00
install_certifi_script ,
prepare_command ,
2021-01-09 15:40:40 -05:00
read_python_configs ,
2022-01-05 02:59:24 +01:00
shell ,
2022-09-06 00:56:20 -04:00
split_config_settings ,
2021-01-08 15:02:20 +00:00
unwrap ,
2022-01-08 11:30:48 +01:00
virtualenv ,
2021-01-06 13:50:58 -05:00
)
2017-03-19 21:27:29 +00:00
2017-04-10 21:42:12 +01:00
2022-07-14 13:36:57 +02:00
def get_macos_version () -> tuple [ int , int ]:
2021-05-03 11:45:43 -04:00
"""
2021-01-05 10:23:36 +00:00
Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0)
These tuples can be used in comparisons, e.g.
(10, 14) <= (11, 0) == True
2021-01-08 14:28:39 +00:00
(10, 14) <= (10, 16) == True
2021-01-05 10:23:36 +00:00
(11, 2) <= (11, 0) != True
2021-05-03 11:45:43 -04:00
"""
2021-01-05 10:23:36 +00:00
version_str , _ , _ = platform . mac_ver ()
version = tuple ( map ( int , version_str . split ( "." )[: 2 ]))
return cast ( Tuple [ int , int ], version )
2022-07-14 13:36:57 +02:00
def get_macos_sdks () -> list [ str ]:
2022-01-05 02:59:24 +01:00
output = call ( "xcodebuild" , "-showsdks" , capture_stdout = True )
2021-05-03 11:45:43 -04:00
return [ m . group ( 1 ) for m in re . finditer ( r "-sdk (macosx\S+)" , output )]
2021-01-30 17:15:44 +00:00
2022-04-28 09:19:27 -04:00
@dataclass ( frozen = True )
class PythonConfiguration :
2020-04-10 01:53:06 +02:00
version : str
identifier : str
url : str
2020-04-08 00:16:25 +02:00
2021-04-30 17:56:34 -04:00
def get_python_configurations (
2022-07-14 13:36:57 +02:00
build_selector : BuildSelector , architectures : set [ Architecture ]
) -> list [ PythonConfiguration ]:
2021-01-09 15:40:40 -05:00
2021-05-03 11:45:43 -04:00
full_python_configs = read_python_configs ( "macos" )
2021-01-09 15:40:40 -05:00
python_configurations = [ PythonConfiguration ( ** item ) for item in full_python_configs ]
2019-04-20 18:52:01 +02:00
2021-01-05 15:14:32 +00:00
# filter out configs that don't match any of the selected architectures
2021-04-30 17:56:34 -04:00
python_configurations = [
c
for c in python_configurations
if any ( c . identifier . endswith ( a . value ) for a in architectures )
]
2021-01-05 15:14:32 +00:00
# skip builds as required by BUILD/SKIP
2021-10-14 10:38:44 -04:00
return [ c for c in python_configurations if build_selector ( c . identifier )]
2019-04-20 18:52:01 +02:00
2022-01-08 11:30:48 +01:00
def install_cpython ( tmp : Path , version : str , url : str ) -> Path :
installation_path = Path ( f "/Library/Frameworks/Python.framework/Versions/ { version } " )
with FileLock ( CIBW_CACHE_PATH / f "cpython { version } .lock" ):
installed_system_packages = call ( "pkgutil" , "--pkgs" , capture_stdout = True ) . splitlines ()
# if this version of python isn't installed, get it from python.org and install
python_package_identifier = f "org.python.Python.PythonFramework- { version } "
if python_package_identifier not in installed_system_packages :
if detect_ci_provider () is None :
# if running locally, we don't want to install CPython with sudo
# let the user know & provide a link to the installer
print (
f "Error: CPython { version } is not installed. \n "
"cibuildwheel will not perform system-wide installs when running outside of CI. \n "
f "To build locally, install CPython { version } on this machine, or, disable this version of Python using CIBW_SKIP=cp { version . replace ( '.' , '' ) } -macosx_* \n "
f " \n Download link: { url } " ,
file = sys . stderr ,
)
raise SystemExit ( 1 )
pkg_path = tmp / "Python.pkg"
# download the pkg
download ( url , pkg_path )
# install
call ( "sudo" , "installer" , "-pkg" , pkg_path , "-target" , "/" )
pkg_path . unlink ()
env = os . environ . copy ()
env [ "PIP_DISABLE_PIP_VERSION_CHECK" ] = "1"
call ( installation_path / "bin" / "python3" , install_certifi_script , env = env )
return installation_path / "bin" / "python3"
2020-02-19 17:09:18 +01:00
2022-02-27 14:16:37 -05:00
def install_pypy ( tmp : Path , url : str ) -> Path :
2021-05-03 11:45:43 -04:00
pypy_tar_bz2 = url . rsplit ( "/" , 1 )[ - 1 ]
2020-06-15 01:53:31 +02:00
extension = ".tar.bz2"
assert pypy_tar_bz2 . endswith ( extension )
2022-01-08 11:30:48 +01:00
installation_path = CIBW_CACHE_PATH / pypy_tar_bz2 [: - len ( extension )]
with FileLock ( str ( installation_path ) + ".lock" ):
if not installation_path . exists ():
downloaded_tar_bz2 = tmp / pypy_tar_bz2
download ( url , downloaded_tar_bz2 )
installation_path . parent . mkdir ( parents = True , exist_ok = True )
call ( "tar" , "-C" , installation_path . parent , "-xf" , downloaded_tar_bz2 )
downloaded_tar_bz2 . unlink ()
return installation_path / "bin" / "pypy3"
2020-02-11 14:44:53 +01:00
2021-04-30 17:56:34 -04:00
def setup_python (
2022-01-08 11:30:48 +01:00
tmp : Path ,
2021-04-30 17:56:34 -04:00
python_configuration : PythonConfiguration ,
dependency_constraint_flags : Sequence [ PathOrStr ],
environment : ParsedEnvironment ,
2021-06-23 10:47:18 -04:00
build_frontend : BuildFrontend ,
2022-07-14 13:36:57 +02:00
) -> dict [ str , str ]:
2022-01-08 11:30:48 +01:00
tmp . mkdir ()
2020-11-13 16:30:27 +00:00
implementation_id = python_configuration . identifier . split ( "-" )[ 0 ]
2021-05-03 11:45:43 -04:00
log . step ( f "Installing Python { implementation_id } ..." )
if implementation_id . startswith ( "cp" ):
2022-01-08 11:30:48 +01:00
base_python = install_cpython ( tmp , python_configuration . version , python_configuration . url )
2021-05-03 11:45:43 -04:00
elif implementation_id . startswith ( "pp" ):
2022-02-27 14:16:37 -05:00
base_python = install_pypy ( tmp , python_configuration . url )
2020-04-05 16:03:35 +02:00
else :
2022-09-05 13:11:46 -04:00
msg = "Unknown Python implementation"
raise ValueError ( msg )
2022-01-08 11:30:48 +01:00
assert base_python . exists ()
2020-04-08 10:24:16 +02:00
2021-05-03 11:45:43 -04:00
log . step ( "Setting up build environment..." )
2022-01-08 11:30:48 +01:00
venv_path = tmp / "venv"
env = virtualenv ( base_python , venv_path , dependency_constraint_flags )
venv_bin_path = venv_path / "bin"
assert venv_bin_path . exists ()
2020-04-05 16:03:35 +02:00
# Fix issue with site.py setting the wrong `sys.prefix`, `sys.exec_prefix`,
# `sys.path`, ... for PyPy: https://foss.heptapod.net/pypy/pypy/issues/3175
# Also fix an issue with the shebang of installed scripts inside the
# testing virtualenv- see https://github.com/theacodes/nox/issues/44 and
# https://github.com/pypa/virtualenv/issues/620
# Also see https://github.com/python/cpython/pull/9516
2021-05-03 11:45:43 -04:00
env . pop ( "__PYVENV_LAUNCHER__" , None )
2020-04-05 16:03:35 +02:00
2021-01-24 15:08:46 +00:00
# we version pip ourselves, so we don't care about pip version checking
2021-05-03 11:45:43 -04:00
env [ "PIP_DISABLE_PIP_VERSION_CHECK" ] = "1"
2021-01-24 15:08:46 +00:00
2021-05-16 10:56:19 +01:00
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
2021-05-08 17:55:10 +01:00
call (
2022-01-05 02:59:24 +01:00
"python" ,
"-m" ,
"pip" ,
"install" ,
2022-01-08 11:30:48 +01:00
"--upgrade" ,
2022-01-05 02:59:24 +01:00
"pip" ,
* dependency_constraint_flags ,
2021-05-08 17:55:10 +01:00
env = env ,
2022-01-08 11:30:48 +01:00
cwd = venv_path ,
2021-05-08 17:55:10 +01:00
)
2021-05-15 17:04:39 +01:00
2021-12-13 09:39:41 -05:00
# Apply our environment after pip is ready
env = environment . as_dictionary ( prev_environment = env )
# check what pip version we're on
2022-01-08 11:30:48 +01:00
assert ( venv_bin_path / "pip" ) . exists ()
2022-01-05 02:59:24 +01:00
call ( "which" , "pip" , env = env )
call ( "pip" , "--version" , env = env )
which_pip = call ( "which" , "pip" , env = env , capture_stdout = True ) . strip ()
2022-01-08 11:30:48 +01:00
if which_pip != str ( venv_bin_path / "pip" ):
2021-04-30 17:56:34 -04:00
print (
"cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it." ,
file = sys . stderr ,
)
2021-01-17 14:13:26 -05:00
sys . exit ( 1 )
2020-04-05 16:03:35 +02:00
2021-12-13 09:39:41 -05:00
# check what Python version we're on
2022-01-05 02:59:24 +01:00
call ( "which" , "python" , env = env )
call ( "python" , "--version" , env = env )
which_python = call ( "which" , "python" , env = env , capture_stdout = True ) . strip ()
2022-01-08 11:30:48 +01:00
if which_python != str ( venv_bin_path / "python" ):
2021-12-13 09:39:41 -05:00
print (
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." ,
file = sys . stderr ,
)
sys . exit ( 1 )
2020-04-07 18:50:16 +01:00
# Set MACOSX_DEPLOYMENT_TARGET to 10.9, if the user didn't set it.
2021-05-01 17:11:09 +02:00
# PyPy defaults to 10.7, causing inconsistencies if it's left unset.
2021-05-03 11:45:43 -04:00
env . setdefault ( "MACOSX_DEPLOYMENT_TARGET" , "10.9" )
2020-04-07 18:50:16 +01:00
2021-05-03 11:45:43 -04:00
config_is_arm64 = python_configuration . identifier . endswith ( "arm64" )
config_is_universal2 = python_configuration . identifier . endswith ( "universal2" )
2021-04-29 20:21:42 -04:00
2021-05-31 03:14:49 -04:00
if python_configuration . version not in { "3.6" , "3.7" }:
if config_is_arm64 :
2021-01-08 14:29:07 +00:00
# macOS 11 is the first OS with arm64 support, so the wheels
# have that as a minimum.
2021-05-03 11:45:43 -04:00
env . setdefault ( "_PYTHON_HOST_PLATFORM" , "macosx-11.0-arm64" )
env . setdefault ( "ARCHFLAGS" , "-arch arm64" )
2021-04-29 20:21:42 -04:00
elif config_is_universal2 :
2021-05-31 03:14:49 -04:00
env . setdefault ( "_PYTHON_HOST_PLATFORM" , "macosx-10.9-universal2" )
env . setdefault ( "ARCHFLAGS" , "-arch arm64 -arch x86_64" )
elif python_configuration . identifier . endswith ( "x86_64" ):
# even on the macos11.0 Python installer, on the x86_64 side it's
# compatible back to 10.9.
env . setdefault ( "_PYTHON_HOST_PLATFORM" , "macosx-10.9-x86_64" )
env . setdefault ( "ARCHFLAGS" , "-arch x86_64" )
2020-12-18 12:53:37 +00:00
2021-04-29 20:21:42 -04:00
building_arm64 = config_is_arm64 or config_is_universal2
2021-05-03 11:45:43 -04:00
if building_arm64 and get_macos_version () < ( 10 , 16 ) and "SDKROOT" not in env :
2021-03-24 18:23:27 -04:00
# xcode 12.2 or higher can build arm64 on macos 10.15 or below, but
# needs the correct SDK selected.
sdks = get_macos_sdks ()
2021-01-26 22:17:34 +00:00
2021-03-24 18:23:27 -04:00
# Different versions of Xcode contain different SDK versions...
# we're happy with anything newer than macOS 11.0
2021-05-03 11:45:43 -04:00
arm64_compatible_sdks = [ s for s in sdks if not s . startswith ( "macosx10." )]
2021-01-30 17:15:44 +00:00
2021-03-24 18:23:27 -04:00
if not arm64_compatible_sdks :
2021-04-30 17:56:34 -04:00
log . warning (
unwrap (
2021-05-03 11:45:43 -04:00
"""
2021-01-30 17:15:44 +00:00
SDK for building arm64-compatible wheels not found. You need Xcode 12.2 or later
to build universal2 or arm64 wheels.
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
)
)
2021-03-24 18:23:27 -04:00
else :
2021-05-03 11:45:43 -04:00
env . setdefault ( "SDKROOT" , arm64_compatible_sdks [ 0 ])
2021-01-24 19:18:23 +00:00
2021-05-03 11:45:43 -04:00
log . step ( "Installing build tools..." )
2021-06-23 10:47:18 -04:00
if build_frontend == "pip" :
call (
2022-01-05 02:59:24 +01:00
"pip" ,
"install" ,
"--upgrade" ,
"setuptools" ,
"wheel" ,
"delocate" ,
* dependency_constraint_flags ,
2021-06-23 10:47:18 -04:00
env = env ,
)
elif build_frontend == "build" :
call (
2022-01-05 02:59:24 +01:00
"pip" ,
"install" ,
"--upgrade" ,
"delocate" ,
"build[virtualenv]" ,
* dependency_constraint_flags ,
2021-06-23 10:47:18 -04:00
env = env ,
)
else :
assert_never ( build_frontend )
2020-11-13 16:30:27 +00:00
2020-04-06 16:55:15 +02:00
return env
2020-04-05 16:03:35 +02:00
2022-01-08 11:30:48 +01:00
def build ( options : Options , tmp_path : Path ) -> None :
2021-10-12 02:05:47 +01:00
python_configurations = get_python_configurations (
options . globals . build_selector , options . globals . architectures
)
2021-09-19 00:19:28 -04:00
2022-04-19 16:00:12 +01:00
if len ( python_configurations ) == 0 :
return
2021-10-12 02:05:47 +01:00
2022-04-19 16:00:12 +01:00
try :
before_all_options_identifier = python_configurations [ 0 ] . identifier
before_all_options = options . build_options ( before_all_options_identifier )
if before_all_options . before_all :
log . step ( "Running before_all..." )
env = before_all_options . environment . as_dictionary ( prev_environment = os . environ )
env . setdefault ( "MACOSX_DEPLOYMENT_TARGET" , "10.9" )
before_all_prepared = prepare_command (
2022-04-21 10:34:11 +01:00
before_all_options . before_all , project = "." , package = before_all_options . package_dir
2022-04-19 16:00:12 +01:00
)
shell ( before_all_prepared , env = env )
2020-05-11 17:09:54 +02:00
2022-07-14 13:36:57 +02:00
built_wheels : list [ Path ] = []
2022-04-18 14:24:53 +02:00
2020-11-13 16:30:27 +00:00
for config in python_configurations :
2021-10-12 02:05:47 +01:00
build_options = options . build_options ( config . identifier )
2020-11-13 16:30:27 +00:00
log . build_start ( config . identifier )
2019-11-17 17:56:07 +01:00
2022-01-08 11:30:48 +01:00
identifier_tmp_dir = tmp_path / config . identifier
identifier_tmp_dir . mkdir ()
built_wheel_dir = identifier_tmp_dir / "built_wheel"
repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel"
2021-05-03 11:45:43 -04:00
config_is_arm64 = config . identifier . endswith ( "arm64" )
config_is_universal2 = config . identifier . endswith ( "universal2" )
2021-04-29 20:21:42 -04:00
2021-01-02 14:32:55 -05:00
dependency_constraint_flags : Sequence [ PathOrStr ] = []
2021-10-12 02:05:47 +01:00
if build_options . dependency_constraints :
2020-11-13 16:30:27 +00:00
dependency_constraint_flags = [
2021-05-03 11:45:43 -04:00
"-c" ,
2021-10-12 02:05:47 +01:00
build_options . dependency_constraints . get_for_python_version ( config . version ),
2020-11-13 16:30:27 +00:00
]
2019-11-19 23:36:20 +01:00
2021-06-23 10:47:18 -04:00
env = setup_python (
2022-01-08 11:30:48 +01:00
identifier_tmp_dir / "build" ,
2021-06-23 10:47:18 -04:00
config ,
dependency_constraint_flags ,
2021-10-12 02:05:47 +01:00
build_options . environment ,
build_options . build_frontend ,
2021-06-23 10:47:18 -04:00
)
2017-04-10 21:42:12 +01:00
2022-06-19 16:34:28 +02:00
compatible_wheel = find_compatible_wheel ( built_wheels , config . identifier )
if compatible_wheel :
2022-04-18 14:24:53 +02:00
log . step_end ()
print (
2022-06-19 16:34:28 +02:00
f " \n Found previously built wheel { compatible_wheel . name } , that's compatible with { config . identifier } . Skipping build step..."
2021-04-30 17:56:34 -04:00
)
2022-06-19 16:34:28 +02:00
repaired_wheel = compatible_wheel
2022-04-18 14:24:53 +02:00
else :
if build_options . before_build :
log . step ( "Running before_build..." )
before_build_prepared = prepare_command (
build_options . before_build , project = "." , package = build_options . package_dir
2021-10-12 02:05:47 +01:00
)
2022-04-18 14:24:53 +02:00
shell ( before_build_prepared , env = env )
2017-06-01 22:53:52 -05:00
2022-04-18 14:24:53 +02:00
log . step ( "Building wheel..." )
built_wheel_dir . mkdir ()
2020-07-20 15:35:51 +01:00
2022-04-18 14:24:53 +02:00
verbosity_flags = get_build_verbosity_extra_flags ( build_options . build_verbosity )
2022-09-06 00:56:20 -04:00
extra_flags = split_config_settings ( build_options . config_settings )
2020-07-20 15:35:51 +01:00
2022-04-18 14:24:53 +02:00
if build_options . build_frontend == "pip" :
2022-09-06 00:56:20 -04:00
extra_flags += verbosity_flags
2022-04-18 14:24:53 +02:00
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369
call (
"python" ,
"-m" ,
"pip" ,
"wheel" ,
build_options . package_dir . resolve (),
f "--wheel-dir= { built_wheel_dir } " ,
"--no-deps" ,
2022-09-06 00:56:20 -04:00
* extra_flags ,
2022-04-18 14:24:53 +02:00
env = env ,
)
elif build_options . build_frontend == "build" :
2022-09-06 00:56:20 -04:00
verbosity_setting = " " . join ( verbosity_flags )
extra_flags += ( f "--config-setting= { verbosity_setting } " ,)
2022-04-18 14:24:53 +02:00
build_env = env . copy ()
if build_options . dependency_constraints :
constraint_path = (
build_options . dependency_constraints . get_for_python_version (
config . version
)
)
build_env [ "PIP_CONSTRAINT" ] = constraint_path . as_uri ()
build_env [ "VIRTUALENV_PIP" ] = get_pip_version ( env )
call (
"python" ,
"-m" ,
"build" ,
build_options . package_dir ,
"--wheel" ,
f "--outdir= { built_wheel_dir } " ,
2022-09-06 00:56:20 -04:00
* extra_flags ,
2022-04-18 14:24:53 +02:00
env = build_env ,
)
2020-12-31 16:48:43 +00:00
else :
2022-04-18 14:24:53 +02:00
assert_never ( build_options . build_frontend )
2020-12-31 16:48:43 +00:00
2022-04-18 14:24:53 +02:00
built_wheel = next ( built_wheel_dir . glob ( "*.whl" ))
2017-03-19 21:27:29 +00:00
2022-04-18 14:24:53 +02:00
repaired_wheel_dir . mkdir ()
2020-03-01 11:09:22 +00:00
2022-04-18 14:24:53 +02:00
if built_wheel . name . endswith ( "none-any.whl" ):
raise NonPlatformWheelError ()
if build_options . repair_command :
log . step ( "Repairing wheel..." )
if config_is_universal2 :
delocate_archs = "x86_64,arm64"
elif config_is_arm64 :
delocate_archs = "arm64"
else :
delocate_archs = "x86_64"
repair_command_prepared = prepare_command (
build_options . repair_command ,
wheel = built_wheel ,
dest_dir = repaired_wheel_dir ,
delocate_archs = delocate_archs ,
)
shell ( repair_command_prepared , env = env )
else :
shutil . move ( str ( built_wheel ), repaired_wheel_dir )
repaired_wheel = next ( repaired_wheel_dir . glob ( "*.whl" ))
2022-06-19 18:03:05 +02:00
if repaired_wheel . name in { wheel . name for wheel in built_wheels }:
raise AlreadyBuiltWheelError ( repaired_wheel . name )
2022-04-18 14:24:53 +02:00
log . step_end ()
2019-10-12 10:35:45 +01:00
2021-10-12 02:05:47 +01:00
if build_options . test_command and build_options . test_selector ( config . identifier ):
2021-01-05 18:56:07 +00:00
machine_arch = platform . machine ()
2022-07-14 13:36:57 +02:00
testing_archs : list [ Literal [ "x86_64" , "arm64" ]]
2019-10-12 10:35:45 +01:00
2021-04-29 20:21:42 -04:00
if config_is_arm64 :
2021-05-03 11:45:43 -04:00
testing_archs = [ "arm64" ]
2021-04-29 20:21:42 -04:00
elif config_is_universal2 :
2021-05-03 11:45:43 -04:00
testing_archs = [ "x86_64" , "arm64" ]
2021-01-26 21:52:02 +00:00
else :
2021-05-03 11:45:43 -04:00
testing_archs = [ "x86_64" ]
2021-01-05 19:02:15 +00:00
2021-01-05 15:58:53 +00:00
for testing_arch in testing_archs :
2021-04-29 20:21:42 -04:00
if config_is_universal2 :
2021-05-03 11:45:43 -04:00
arch_specific_identifier = f " { config . identifier } : { testing_arch } "
2021-10-12 02:05:47 +01:00
if not build_options . test_selector ( arch_specific_identifier ):
2021-01-26 21:52:02 +00:00
continue
2021-05-03 11:45:43 -04:00
if machine_arch == "x86_64" and testing_arch == "arm64" :
2021-04-29 20:21:42 -04:00
if config_is_arm64 :
2021-04-30 17:56:34 -04:00
log . warning (
unwrap (
2021-05-03 11:45:43 -04:00
"""
2021-05-02 16:37:38 +02:00
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. To silence this warning, set
`CIBW_TEST_SKIP: *-macosx_arm64`.
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
)
)
2021-04-29 20:21:42 -04:00
elif config_is_universal2 :
2021-04-30 17:56:34 -04:00
log . warning (
unwrap (
2021-05-03 11:45:43 -04:00
"""
2021-05-02 16:37:38 +02:00
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. To silence this warning, set
`CIBW_TEST_SKIP: *-macosx_universal2:arm64`.
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
)
)
2021-01-26 21:52:02 +00:00
else :
2022-09-05 13:11:46 -04:00
msg = "unreachable"
raise RuntimeError ( msg )
2021-01-26 21:52:02 +00:00
# skip this test
continue
2022-07-14 11:42:40 +02:00
if testing_arch == "arm64" and config . identifier . startswith ( "cp38-" ):
log . warning (
unwrap (
"""
While cibuildwheel can build CPython 3.8 universal2/arm64 wheels, we
cannot test the arm64 part of them, even when running on an Apple
Silicon machine. This is because we use the x86_64 installer of
CPython 3.8. See the discussion in
https://github.com/pypa/cibuildwheel/pull/1169 for the details. To
silence this warning, set `CIBW_TEST_SKIP: cp38-macosx_*:arm64`.
"""
)
)
# skip this test
continue
2021-04-30 17:56:34 -04:00
log . step (
2021-05-03 11:45:43 -04:00
"Testing wheel..."
2021-04-30 17:56:34 -04:00
if testing_arch == machine_arch
2021-05-03 11:45:43 -04:00
else f "Testing wheel on { testing_arch } ..."
2021-04-30 17:56:34 -04:00
)
2020-01-07 00:17:03 +01:00
2021-01-05 15:58:53 +00:00
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
2022-01-05 02:59:24 +01:00
call ( "pip" , "install" , "virtualenv" , * dependency_constraint_flags , env = env )
2022-01-08 11:30:48 +01:00
venv_dir = identifier_tmp_dir / "venv-test"
2019-10-12 10:35:45 +01:00
2021-01-05 15:58:53 +00:00
arch_prefix = []
if testing_arch != machine_arch :
2021-05-03 11:45:43 -04:00
if machine_arch == "arm64" and testing_arch == "x86_64" :
2021-01-05 15:58:53 +00:00
# rosetta2 will provide the emulation with just the arch prefix.
2021-05-03 11:45:43 -04:00
arch_prefix = [ "arch" , "-x86_64" ]
2021-01-05 15:58:53 +00:00
else :
2022-02-27 14:16:37 -05:00
msg = f "don't know how to emulate { testing_arch } on { machine_arch } "
raise RuntimeError ( msg )
2019-10-12 10:44:40 +01:00
2021-01-05 15:58:53 +00:00
# define a custom 'call' function that adds the arch prefix each time
2022-02-27 14:16:37 -05:00
call_with_arch = functools . partial ( call , * arch_prefix )
2022-07-25 16:15:00 +02:00
shell_with_arch = functools . partial ( call , * arch_prefix , "/bin/sh" , "-c" )
2019-10-15 21:40:00 +02:00
2021-01-05 15:58:53 +00:00
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
2022-01-05 02:59:24 +01:00
call_with_arch ( "python" , "-m" , "virtualenv" , "--no-download" , venv_dir , env = env )
2017-07-02 18:02:27 -05:00
2021-01-05 15:58:53 +00:00
virtualenv_env = env . copy ()
2021-05-03 11:45:43 -04:00
virtualenv_env [ "PATH" ] = os . pathsep . join (
2021-04-30 17:56:34 -04:00
[
2021-05-03 11:45:43 -04:00
str ( venv_dir / "bin" ),
virtualenv_env [ "PATH" ],
2021-04-30 17:56:34 -04:00
]
)
2021-01-05 15:58:53 +00:00
# check that we are using the Python from the virtual environment
2022-01-05 02:59:24 +01:00
call_with_arch ( "which" , "python" , env = virtualenv_env )
2021-01-05 15:58:53 +00:00
2021-10-12 02:05:47 +01:00
if build_options . before_test :
2021-04-30 17:56:34 -04:00
before_test_prepared = prepare_command (
2021-10-12 02:05:47 +01:00
build_options . before_test ,
project = "." ,
package = build_options . package_dir ,
2021-04-30 17:56:34 -04:00
)
2022-01-05 02:59:24 +01:00
shell_with_arch ( before_test_prepared , env = virtualenv_env )
2021-01-05 15:58:53 +00:00
# install the wheel
2021-04-30 17:56:34 -04:00
call_with_arch (
2022-01-05 02:59:24 +01:00
"pip" ,
"install" ,
f " { repaired_wheel }{ build_options . test_extras } " ,
2021-04-30 17:56:34 -04:00
env = virtualenv_env ,
)
2021-01-05 15:58:53 +00:00
# test the wheel
2021-10-12 02:05:47 +01:00
if build_options . test_requires :
2021-04-30 17:56:34 -04:00
call_with_arch (
2022-01-05 02:59:24 +01:00
"pip" , "install" , * build_options . test_requires , env = virtualenv_env
2021-04-30 17:56:34 -04:00
)
2021-01-05 15:58:53 +00:00
# run the tests from $HOME, with an absolute path in the command
# (this ensures that Python runs the tests against the installed wheel
# and not the repo code)
test_command_prepared = prepare_command (
2021-10-12 02:05:47 +01:00
build_options . test_command ,
2021-05-03 11:45:43 -04:00
project = Path ( "." ) . resolve (),
2021-10-12 02:05:47 +01:00
package = build_options . package_dir . resolve (),
2021-04-30 17:56:34 -04:00
)
2022-01-05 02:59:24 +01:00
shell_with_arch (
test_command_prepared , cwd = os . environ [ "HOME" ], env = virtualenv_env
2021-01-05 15:58:53 +00:00
)
2020-11-13 16:30:27 +00:00
# we're all done here; move it to output (overwrite existing)
2022-06-19 16:34:28 +02:00
if compatible_wheel is None :
2022-06-09 12:21:42 -05:00
try :
2022-06-16 10:39:05 -05:00
( build_options . output_dir / repaired_wheel . name ) . unlink ()
2022-06-16 10:40:48 -05:00
except FileNotFoundError :
2022-06-09 12:21:42 -05:00
pass
2022-04-18 14:24:53 +02:00
shutil . move ( str ( repaired_wheel ), build_options . output_dir )
built_wheels . append ( build_options . output_dir / repaired_wheel . name )
2022-01-08 11:30:48 +01:00
# clean up
shutil . rmtree ( identifier_tmp_dir )
2020-11-13 16:30:27 +00:00
log . build_end ()
except subprocess . CalledProcessError as error :
2021-04-30 17:56:34 -04:00
log . step_end_with_error (
2021-05-03 11:45:43 -04:00
f "Command { error . cmd } failed with code { error . returncode } . { error . stdout } "
2021-04-30 17:56:34 -04:00
)
2021-01-17 14:13:26 -05:00
sys . exit ( 1 )