2022-07-14 13:36:57 +02:00
from __future__ import annotations
2022-10-07 08:47:31 -04:00
import contextlib
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
2023-04-18 12:38:21 -04:00
import typing
from collections.abc import Sequence , Set
2022-04-28 09:19:27 -04:00
from dataclasses import dataclass
2020-06-15 01:53:31 +02:00
from pathlib import Path
2023-06-14 00:02:10 +02:00
from typing import Literal , Tuple
2020-04-08 00:16:25 +02:00
2022-01-08 11:30:48 +01:00
from filelock import FileLock
2024-05-27 23:04:25 +02:00
from packaging.version import Version
2022-01-08 11:30:48 +01:00
2023-06-14 00:02:10 +02:00
from ._compat.typing import assert_never
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
2023-04-18 23:05:34 -04:00
from .typing import PathOrStr
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 ,
2023-08-26 19:37:29 +01:00
BuildFrontendConfig ,
BuildFrontendName ,
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 ,
2022-11-06 00:01:30 +00:00
test_fail_cwd_file ,
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 ]))
2023-04-18 12:38:21 -04:00
return typing . cast ( Tuple [ int , int ], version )
2021-01-05 10:23:36 +00:00
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 (
2023-04-06 11:40:25 -07:00
build_selector : BuildSelector , architectures : Set [ Architecture ]
2022-07-14 13:36:57 +02:00
) -> list [ PythonConfiguration ]:
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
2023-01-14 09:48:01 +01:00
python_configurations = [ c for c in python_configurations if build_selector ( c . identifier )]
# filter-out some cross-compilation configs with PyPy:
# can't build arm64 on x86_64
# rosetta allows to build x86_64 on arm64
if platform . machine () == "x86_64" :
python_configurations_before = set ( python_configurations )
python_configurations = [
c
for c in python_configurations
if not ( c . identifier . startswith ( "pp" ) and c . identifier . endswith ( "arm64" ))
]
removed_elements = python_configurations_before - set ( python_configurations )
if removed_elements :
ids = ", " . join ( c . identifier for c in removed_elements )
log . quiet (
unwrap (
f """
Note: { ids } { 'was' if len ( removed_elements ) == 1 else 'were' }
selected, but can't be built on x86_64 so will be skipped automatically.
"""
)
)
return python_configurations
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 ,
2023-08-26 19:37:29 +01:00
build_frontend : BuildFrontendName ,
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"
2024-05-20 08:21:42 +02:00
env = virtualenv (
python_configuration . version , base_python , venv_path , dependency_constraint_flags
)
2022-01-08 11:30:48 +01:00
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 )
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
2022-10-14 23:56:57 +02:00
# Set MACOSX_DEPLOYMENT_TARGET, if the user didn't set it.
# For arm64, the minimal deployment target is 11.0.
# On x86_64 (or universal2), use 10.9 as a default.
# PyPy defaults to 10.7, causing inconsistencies if it's left unset.
env . setdefault ( "MACOSX_DEPLOYMENT_TARGET" , "11.0" if config_is_arm64 else "10.9" )
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" ,
"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-10-07 08:47:31 -04:00
if not python_configurations :
2022-04-19 16:00:12 +01:00
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 )
2023-08-26 19:37:29 +01:00
build_frontend = build_options . build_frontend or BuildFrontendConfig ( "pip" )
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 ,
2023-08-26 19:37:29 +01:00
build_frontend . name ,
2021-06-23 10:47:18 -04:00
)
2024-05-27 23:04:25 +02:00
pip_version = get_pip_version ( env )
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
2023-08-26 19:37:29 +01:00
extra_flags = split_config_settings (
build_options . config_settings , build_frontend . name
)
extra_flags += build_frontend . args
2020-07-20 15:35:51 +01:00
2024-05-10 21:33:56 +02:00
build_env = env . copy ()
2024-05-27 23:04:25 +02:00
build_env [ "VIRTUALENV_PIP" ] = pip_version
2024-05-10 21:33:56 +02:00
if build_options . dependency_constraints :
constraint_path = build_options . dependency_constraints . get_for_python_version (
config . version
)
user_constraints = build_env . get ( "PIP_CONSTRAINT" )
our_constraints = constraint_path . as_uri ()
build_env [ "PIP_CONSTRAINT" ] = " " . join (
c for c in [ user_constraints , our_constraints ] if c
)
2023-08-26 19:37:29 +01:00
if build_frontend . name == "pip" :
2023-04-17 11:06:45 -07:00
extra_flags += get_build_verbosity_extra_flags ( build_options . build_verbosity )
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 ,
2024-05-10 21:33:56 +02:00
env = build_env ,
2022-04-18 14:24:53 +02:00
)
2023-08-26 19:37:29 +01:00
elif build_frontend . name == "build" :
2023-04-17 11:06:45 -07:00
if not 0 <= build_options . build_verbosity < 2 :
msg = f "build_verbosity { build_options . build_verbosity } is not supported for build frontend. Ignoring."
log . warning ( msg )
2022-04-18 14:24:53 +02:00
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 :
2023-04-07 15:51:12 -07:00
assert_never ( 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-09-25 10:42:19 +02:00
python_arch = call (
"python" ,
"-sSc" ,
"import platform; print(platform.machine())" ,
env = env ,
capture_stdout = True ,
) . strip ()
2023-02-07 10:25:34 -05: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
2024-02-10 09:55:38 +01:00
tested. Consider building arm64 wheels natively, if your CI
provider offers this. To silence this warning, set
2023-11-18 12:37:50 +02:00
`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
2024-02-10 09:55:38 +01:00
of the wheel cannot be tested on x86_64. Consider building
universal2 wheels on an arm64 runner, if your CI provider offers
this. Notably, an arm64 runner can also test the x86_64 part of
the wheel, through Rosetta emulation. 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-09-25 10:42:19 +02:00
is_cp38 = config . identifier . startswith ( "cp38-" )
if testing_arch == "arm64" and is_cp38 and python_arch != "arm64" :
2022-07-14 11:42:40 +02:00
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
2023-11-18 12:37:50 +02:00
silence this warning, set `CIBW_TEST_SKIP: "cp38-macosx_*:arm64"`.
2022-07-14 11:42:40 +02:00
"""
)
)
# 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
2024-02-01 00:33:33 -05:00
venv_dir = identifier_tmp_dir / f "venv-test- { testing_arch } "
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
2024-05-27 23:04:25 +02:00
# Use pip version from the initial env to ensure determinism
venv_args = [ "--no-periodic-update" , f "--pip= { pip_version } " ]
# In Python<3.12, setuptools & wheel are installed as well, use virtualenv embedded ones
if Version ( config . version ) < Version ( "3.12" ):
venv_args . extend (( "--setuptools=embed" , "--wheel=embed" ))
call_with_arch ( "python" , "-m" , "virtualenv" , * venv_args , 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
2024-03-11 09:36:36 +08:00
if is_cp38 and python_arch == "x86_64" :
virtualenv_env_install_wheel = virtualenv_env . copy ()
virtualenv_env_install_wheel [ "SYSTEM_VERSION_COMPAT" ] = "0"
log . notice (
unwrap (
"""
Setting SYSTEM_VERSION_COMPAT=0 to ensure CPython 3.8 can get
correct macOS version and allow installation of wheels with
MACOSX_DEPLOYMENT_TARGET >= 11.0.
See https://github.com/pypa/cibuildwheel/issues/1767 for the
details.
"""
)
)
else :
virtualenv_env_install_wheel = virtualenv_env
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 } " ,
2024-03-11 09:36:36 +08:00
env = virtualenv_env_install_wheel ,
2021-04-30 17:56:34 -04:00
)
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 (
2024-03-11 09:36:36 +08:00
"pip" ,
"install" ,
* build_options . test_requires ,
env = virtualenv_env_install_wheel ,
2021-04-30 17:56:34 -04:00
)
2021-01-05 15:58:53 +00:00
2022-11-06 00:01:30 +00:00
# run the tests from a temp dir, with an absolute path in the command
2021-01-05 15:58:53 +00:00
# (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 (),
2023-06-23 08:29:58 -04:00
wheel = repaired_wheel ,
2021-04-30 17:56:34 -04:00
)
2022-11-06 00:01:30 +00:00
test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd . mkdir ( exist_ok = True )
( test_cwd / "test_fail.py" ) . write_text ( test_fail_cwd_file . read_text ())
shell_with_arch ( test_command_prepared , cwd = test_cwd , 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-10-07 08:47:31 -04:00
with contextlib . suppress ( FileNotFoundError ):
2022-06-16 10:39:05 -05:00
( build_options . output_dir / repaired_wheel . name ) . unlink ()
2022-06-09 12:21:42 -05:00
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 )