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 shlex
import shutil
import subprocess
2020-02-19 17:56:11 +01:00
import sys
2019-09-07 20:20:31 +01:00
import tempfile
2020-06-15 01:53:31 +02:00
from pathlib import Path
2021-01-06 21:39:32 +00:00
from typing import Any , Dict , List , NamedTuple , Optional , Sequence , Set , Tuple , cast
2020-04-08 00:16:25 +02:00
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-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 (
2021-09-19 00:19:28 -04:00
AllBuildOptions ,
2021-06-23 10:47:18 -04:00
BuildFrontend ,
2021-01-06 13:50:58 -05:00
BuildSelector ,
NonPlatformWheelError ,
download ,
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 ,
2021-01-08 15:02:20 +00:00
unwrap ,
2021-01-06 13:50:58 -05:00
)
2017-03-19 21:27:29 +00:00
2017-04-10 21:42:12 +01:00
2021-04-30 17:56:34 -04:00
def call (
args : Sequence [ PathOrStr ],
env : Optional [ Dict [ str , str ]] = None ,
cwd : Optional [ str ] = None ,
shell : bool = False ,
) -> None :
2020-02-11 14:44:53 +01:00
# print the command executing for the logs
if shell :
2021-05-03 11:45:43 -04:00
print ( f "+ { args } " )
2020-02-11 14:44:53 +01:00
else :
2021-05-03 11:45:43 -04:00
print ( "+ " + " " . join ( shlex . quote ( str ( a )) for a in args ))
2020-02-11 14:44:53 +01:00
2021-02-14 12:56:33 -05:00
subprocess . run ( args , env = env , cwd = cwd , shell = shell , check = True )
2020-02-11 14:44:53 +01:00
2021-01-05 10:23:36 +00: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 )
2021-01-30 17:15:44 +00:00
def get_macos_sdks () -> List [ str ]:
2021-02-14 12:56:33 -05:00
output = subprocess . run (
2021-05-03 11:45:43 -04:00
[ "xcodebuild" , "-showsdks" ],
2021-01-30 17:15:44 +00:00
universal_newlines = True ,
2021-02-14 12:56:33 -05:00
check = True ,
stdout = subprocess . PIPE ,
) . stdout
2021-01-30 17:15:44 +00:00
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
2020-04-10 01:53:06 +02:00
class PythonConfiguration ( NamedTuple ):
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 (
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-01-05 14:29:38 +00:00
python_configurations = [ c for c in python_configurations if build_selector ( c . identifier )]
2021-01-08 15:18:37 +00:00
# When running on macOS 11 and x86_64, the reported OS is '10.16', but
# there is no such OS - it really means macOS 11.
2021-01-08 14:28:39 +00:00
if get_macos_version () >= ( 10 , 16 ):
2021-05-03 11:45:43 -04:00
if any ( c . identifier . startswith ( "pp" ) for c in python_configurations ):
2021-01-05 15:14:32 +00:00
# pypy doesn't work on macOS 11 yet
# See https://foss.heptapod.net/pypy/pypy/-/issues/3314
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
PyPy is currently unsupported when building on macOS 11. To build macOS PyPy
wheels, build on an older OS, such as macOS 10.15. To silence this warning,
deselect PyPy by adding "pp*-macosx*" to your CIBW_SKIP option.
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
)
)
python_configurations = [
2021-05-03 11:45:43 -04:00
c for c in python_configurations if not c . identifier . startswith ( "pp" )
2021-04-30 17:56:34 -04:00
]
2021-01-05 14:29:38 +00:00
return python_configurations
2019-04-20 18:52:01 +02:00
2021-05-03 11:45:43 -04:00
SYMLINKS_DIR = Path ( "/tmp/cibw_bin" )
2020-02-19 17:09:18 +01:00
2020-06-15 01:53:31 +02:00
def make_symlinks ( installation_bin_path : Path , python_executable : str , pip_executable : str ) -> None :
assert ( installation_bin_path / python_executable ) . exists ()
2020-02-16 14:32:19 +01:00
# Python bin folders on Mac don't symlink `python3` to `python`, and neither
# does PyPy for `pypy` or `pypy3`, so we do that so `python` and `pip` always
# point to the active configuration.
2020-06-15 01:53:31 +02:00
if SYMLINKS_DIR . exists ():
2020-02-19 17:09:18 +01:00
shutil . rmtree ( SYMLINKS_DIR )
2020-06-15 01:53:31 +02:00
SYMLINKS_DIR . mkdir ( parents = True )
2020-02-16 14:32:19 +01:00
2021-05-03 11:45:43 -04:00
( SYMLINKS_DIR / "python" ) . symlink_to ( installation_bin_path / python_executable )
( SYMLINKS_DIR / "python-config" ) . symlink_to (
installation_bin_path / ( python_executable + "-config" )
2021-04-30 17:56:34 -04:00
)
2021-05-03 11:45:43 -04:00
( SYMLINKS_DIR / "pip" ) . symlink_to ( installation_bin_path / pip_executable )
2020-02-16 14:32:19 +01:00
2020-06-15 01:53:31 +02:00
def install_cpython ( version : str , url : str ) -> Path :
2021-04-30 17:56:34 -04:00
installed_system_packages = subprocess . run (
2021-05-03 11:45:43 -04:00
[ "pkgutil" , "--pkgs" ], universal_newlines = True , check = True , stdout = subprocess . PIPE
2021-04-30 17:56:34 -04:00
) . stdout . splitlines ()
2020-02-11 14:44:53 +01:00
# if this version of python isn't installed, get it from python.org and install
2021-05-03 11:45:43 -04:00
python_package_identifier = f "org.python.Python.PythonFramework- { version } "
2021-02-14 20:44:20 +01:00
python_executable = "python3"
2021-05-03 11:45:43 -04:00
installation_bin_path = Path ( f "/Library/Frameworks/Python.framework/Versions/ { version } /bin" )
2020-10-10 18:24:15 +02:00
2020-02-11 14:44:53 +01:00
if python_package_identifier not in installed_system_packages :
# download the pkg
2021-05-03 11:45:43 -04:00
download ( url , Path ( "/tmp/Python.pkg" ))
2020-02-11 14:44:53 +01:00
# install
2021-05-03 11:45:43 -04:00
call ([ "sudo" , "installer" , "-pkg" , "/tmp/Python.pkg" , "-target" , "/" ])
2021-04-30 17:56:34 -04:00
call ([ "sudo" , str ( installation_bin_path / python_executable ), str ( install_certifi_script )])
2020-10-10 18:24:15 +02:00
2021-02-14 20:44:20 +01:00
pip_executable = "pip3"
2020-02-19 17:09:18 +01:00
make_symlinks ( installation_bin_path , python_executable , pip_executable )
2020-02-16 14:32:19 +01:00
return installation_bin_path
2020-02-11 14:44:53 +01:00
2020-06-15 01:53:31 +02:00
def install_pypy ( version : str , 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 )
2021-04-30 17:56:34 -04:00
pypy_base_filename = pypy_tar_bz2 [: - len ( extension )]
2021-05-03 11:45:43 -04:00
installation_path = Path ( "/tmp" ) / pypy_base_filename
2020-06-15 01:53:31 +02:00
if not installation_path . exists ():
downloaded_tar_bz2 = Path ( "/tmp" ) / pypy_tar_bz2
download ( url , downloaded_tar_bz2 )
2021-05-03 11:45:43 -04:00
call ([ "tar" , "-C" , "/tmp" , "-xf" , downloaded_tar_bz2 ])
2020-02-16 14:32:19 +01:00
2021-05-03 11:45:43 -04:00
installation_bin_path = installation_path / "bin"
2021-02-14 20:44:20 +01:00
python_executable = "pypy3"
pip_executable = "pip3"
2020-02-19 17:09:18 +01:00
make_symlinks ( installation_bin_path , python_executable , pip_executable )
2020-02-16 14:32:19 +01:00
return installation_bin_path
2020-02-11 14:44:53 +01:00
2021-04-30 17:56:34 -04:00
def setup_python (
python_configuration : PythonConfiguration ,
dependency_constraint_flags : Sequence [ PathOrStr ],
environment : ParsedEnvironment ,
2021-06-23 10:47:18 -04:00
build_frontend : BuildFrontend ,
2021-04-30 17:56:34 -04:00
) -> Dict [ str , str ]:
2021-06-23 10:47:18 -04:00
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 } ..." )
2020-11-13 16:30:27 +00:00
2021-05-03 11:45:43 -04:00
if implementation_id . startswith ( "cp" ):
2021-04-30 17:56:34 -04:00
installation_bin_path = install_cpython (
python_configuration . version , python_configuration . url
)
2021-05-03 11:45:43 -04:00
elif implementation_id . startswith ( "pp" ):
2020-04-05 16:03:35 +02:00
installation_bin_path = install_pypy ( python_configuration . version , python_configuration . url )
else :
raise ValueError ( "Unknown Python implementation" )
2020-04-08 10:24:16 +02:00
2021-05-03 11:45:43 -04:00
log . step ( "Setting up build environment..." )
2020-11-13 16:30:27 +00:00
2020-04-05 16:03:35 +02:00
env = os . environ . copy ()
2021-05-03 11:45:43 -04:00
env [ "PATH" ] = os . pathsep . join (
2021-04-30 17:56:34 -04:00
[
str ( SYMLINKS_DIR ),
str ( installation_bin_path ),
2021-05-03 11:45:43 -04:00
env [ "PATH" ],
2021-04-30 17:56:34 -04:00
]
)
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
env = environment . as_dictionary ( prev_environment = env )
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
2020-04-05 16:03:35 +02:00
# check what version we're on
2021-05-03 11:45:43 -04:00
call ([ "which" , "python" ], env = env )
call ([ "python" , "--version" ], env = env )
2021-04-30 17:56:34 -04:00
which_python = subprocess . run (
2021-05-03 11:45:43 -04:00
[ "which" , "python" ], env = env , universal_newlines = True , check = True , stdout = subprocess . PIPE
2021-04-30 17:56:34 -04:00
) . stdout . strip ()
2021-05-03 11:45:43 -04:00
if which_python != "/tmp/cibw_bin/python" :
2021-04-30 17:56:34 -04: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 ,
)
2021-01-17 14:13:26 -05:00
sys . exit ( 1 )
2020-04-05 16:03:35 +02:00
2021-05-16 10:56:19 +01:00
# Install pip
2021-05-15 17:04:39 +01:00
2021-05-16 10:12:08 +01:00
requires_reinstall = not ( installation_bin_path / "pip" ) . exists ()
2021-05-16 10:56:19 +01:00
if requires_reinstall :
# maybe pip isn't installed at all. ensurepip resolves that.
call ([ "python" , "-m" , "ensurepip" ], env = env , cwd = "/tmp" )
# 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 (
2021-05-16 10:12:08 +01:00
[
"python" ,
"-m" ,
"pip" ,
"install" ,
"--force-reinstall" if requires_reinstall else "--upgrade" ,
"pip" ,
* dependency_constraint_flags ,
],
2021-05-08 17:55:10 +01:00
env = env ,
cwd = "/tmp" ,
)
2021-05-15 17:04:39 +01:00
2021-05-03 11:45:43 -04:00
assert ( installation_bin_path / "pip" ) . exists ()
call ([ "which" , "pip" ], env = env )
call ([ "pip" , "--version" ], env = env )
2021-04-30 17:56:34 -04:00
which_pip = subprocess . run (
2021-05-03 11:45:43 -04:00
[ "which" , "pip" ], env = env , universal_newlines = True , check = True , stdout = subprocess . PIPE
2021-04-30 17:56:34 -04:00
) . stdout . strip ()
2021-05-03 11:45:43 -04:00
if which_pip != "/tmp/cibw_bin/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
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 (
[
"pip" ,
"install" ,
"--upgrade" ,
"setuptools" ,
"wheel" ,
"delocate" ,
* dependency_constraint_flags ,
],
env = env ,
)
elif build_frontend == "build" :
call (
[
"pip" ,
"install" ,
"--upgrade" ,
"delocate" ,
"build[virtualenv]" ,
* dependency_constraint_flags ,
],
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
2021-09-19 00:19:28 -04:00
def build ( all_options : AllBuildOptions ) -> None :
2021-05-03 11:45:43 -04:00
temp_dir = Path ( tempfile . mkdtemp ( prefix = "cibuildwheel" ))
built_wheel_dir = temp_dir / "built_wheel"
repaired_wheel_dir = temp_dir / "repaired_wheel"
2019-11-12 23:34:59 +00:00
2021-09-19 00:19:28 -04:00
all_options . check_build_selectors ()
2020-11-13 16:30:27 +00:00
try :
2021-09-19 00:19:28 -04:00
if all_options . before_all :
2021-05-03 11:45:43 -04:00
log . step ( "Running before_all..." )
2021-09-19 00:19:28 -04:00
env = all_options . environment . as_dictionary ( prev_environment = os . environ )
2021-05-03 11:45:43 -04:00
env . setdefault ( "MACOSX_DEPLOYMENT_TARGET" , "10.9" )
2021-04-30 17:56:34 -04:00
before_all_prepared = prepare_command (
2021-09-19 00:19:28 -04:00
all_options . before_all , project = "." , package = all_options . package_dir
2021-04-30 17:56:34 -04:00
)
2020-11-13 16:30:27 +00:00
call ([ before_all_prepared ], shell = True , env = env )
2020-05-11 17:09:54 +02:00
2021-04-30 17:56:34 -04:00
python_configurations = get_python_configurations (
2021-09-19 00:19:28 -04:00
all_options . build_selector , all_options . architectures
2021-04-30 17:56:34 -04:00
)
2018-03-31 13:02:42 +02:00
2020-11-13 16:30:27 +00:00
for config in python_configurations :
2021-09-19 00:19:28 -04:00
options = all_options [ config . identifier ]
2020-11-13 16:30:27 +00:00
log . build_start ( config . identifier )
2019-11-17 17:56:07 +01:00
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 ] = []
2020-11-13 16:30:27 +00:00
if options . dependency_constraints :
dependency_constraint_flags = [
2021-05-03 11:45:43 -04:00
"-c" ,
2021-04-30 17:56:34 -04:00
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 (
config ,
dependency_constraint_flags ,
options . environment ,
options . build_frontend ,
)
2017-04-10 21:42:12 +01:00
2020-11-13 16:30:27 +00:00
if options . before_build :
2021-05-03 11:45:43 -04:00
log . step ( "Running before_build..." )
2021-04-30 17:56:34 -04:00
before_build_prepared = prepare_command (
2021-05-03 11:45:43 -04:00
options . before_build , project = "." , package = options . package_dir
2021-04-30 17:56:34 -04:00
)
2020-11-13 16:30:27 +00:00
call ( before_build_prepared , env = env , shell = True )
2020-06-23 20:28:54 +01:00
2021-05-03 11:45:43 -04:00
log . step ( "Building wheel..." )
2020-11-13 16:30:27 +00:00
if built_wheel_dir . exists ():
shutil . rmtree ( built_wheel_dir )
built_wheel_dir . mkdir ( parents = True )
2020-06-25 12:41:21 +01:00
2021-06-23 10:47:18 -04:00
verbosity_flags = get_build_verbosity_extra_flags ( options . build_verbosity )
if options . build_frontend == "pip" :
# 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" ,
options . package_dir . resolve (),
f "--wheel-dir= { built_wheel_dir } " ,
"--no-deps" ,
* verbosity_flags ,
],
env = env ,
)
elif options . build_frontend == "build" :
config_setting = " " . join ( verbosity_flags )
build_env = env . copy ()
if options . dependency_constraints :
2021-07-09 09:16:20 -04:00
constr = options . dependency_constraints . get_for_python_version ( config . version )
build_env [ "PIP_CONSTRAINT" ] = constr . as_uri ()
2021-06-23 10:47:18 -04:00
build_env [ "VIRTUALENV_PIP" ] = get_pip_version ( env )
call (
[
"python" ,
"-m" ,
"build" ,
options . package_dir ,
"--wheel" ,
f "--outdir= { built_wheel_dir } " ,
f "--config-setting= { config_setting } " ,
],
env = build_env ,
)
else :
assert_never ( options . build_frontend )
2017-06-01 22:53:52 -05:00
2021-05-03 11:45:43 -04:00
built_wheel = next ( built_wheel_dir . glob ( "*.whl" ))
2020-07-20 15:35:51 +01:00
2020-11-13 16:30:27 +00:00
if repaired_wheel_dir . exists ():
shutil . rmtree ( repaired_wheel_dir )
repaired_wheel_dir . mkdir ( parents = True )
2020-07-20 15:35:51 +01:00
2021-05-03 11:45:43 -04:00
if built_wheel . name . endswith ( "none-any.whl" ):
2020-11-13 16:30:27 +00:00
raise NonPlatformWheelError ()
2020-07-20 15:35:51 +01:00
2020-11-13 16:30:27 +00:00
if options . repair_command :
2021-05-03 11:45:43 -04:00
log . step ( "Repairing wheel..." )
2020-12-31 16:48:43 +00:00
2021-04-29 20:21:42 -04:00
if config_is_universal2 :
2021-05-03 11:45:43 -04:00
delocate_archs = "x86_64,arm64"
2021-04-29 20:21:42 -04:00
elif config_is_arm64 :
2021-05-03 11:45:43 -04:00
delocate_archs = "arm64"
2020-12-31 16:48:43 +00:00
else :
2021-05-03 11:45:43 -04:00
delocate_archs = "x86_64"
2020-12-31 16:48:43 +00:00
repair_command_prepared = prepare_command (
options . repair_command ,
wheel = built_wheel ,
dest_dir = repaired_wheel_dir ,
delocate_archs = delocate_archs ,
)
2020-11-13 16:30:27 +00:00
call ( repair_command_prepared , env = env , shell = True )
else :
shutil . move ( str ( built_wheel ), repaired_wheel_dir )
2017-03-19 21:27:29 +00:00
2021-05-03 11:45:43 -04:00
repaired_wheel = next ( repaired_wheel_dir . glob ( "*.whl" ))
2020-03-01 11:09:22 +00:00
2021-01-05 15:58:53 +00:00
log . step_end ()
2019-10-12 10:35:45 +01:00
2021-01-20 21:22:48 -05:00
if options . test_command and options . test_selector ( config . identifier ):
2021-01-05 18:56:07 +00:00
machine_arch = platform . machine ()
2021-05-03 11:45:43 -04: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-01-26 21:52:02 +00:00
if not options . test_selector ( arch_specific_identifier ):
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 :
2021-05-03 11:45:43 -04:00
raise RuntimeError ( "unreachable" )
2021-01-26 21:52:02 +00: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.
2021-05-03 11:45:43 -04:00
call ([ "pip" , "install" , "virtualenv" , * dependency_constraint_flags ], env = env )
2021-01-05 15:58:53 +00:00
venv_dir = Path ( tempfile . mkdtemp ())
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 :
2021-04-30 17:56:34 -04:00
raise RuntimeError (
"don't know how to emulate {testing_arch} on {machine_arch} "
)
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
2021-02-14 12:56:33 -05:00
def call_with_arch ( args : Sequence [ PathOrStr ], ** kwargs : Any ) -> None :
2021-01-05 15:58:53 +00:00
if isinstance ( args , str ):
2021-05-03 11:45:43 -04:00
args = " " . join ( arch_prefix ) + " " + args
2021-01-05 15:58:53 +00:00
else :
args = [ * arch_prefix , * args ]
2021-02-14 12:56:33 -05:00
call ( args , ** kwargs )
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
2021-04-30 17:56:34 -04:00
call_with_arch (
2021-05-03 11:45:43 -04:00
[ "python" , "-m" , "virtualenv" , "--no-download" , venv_dir ], env = env
2021-04-30 17:56:34 -04:00
)
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
2021-05-03 11:45:43 -04:00
call_with_arch ([ "which" , "python" ], env = virtualenv_env )
2021-01-05 15:58:53 +00:00
if options . before_test :
2021-04-30 17:56:34 -04:00
before_test_prepared = prepare_command (
2021-05-03 11:45:43 -04:00
options . before_test , project = "." , package = options . package_dir
2021-04-30 17:56:34 -04:00
)
2021-01-05 15:58:53 +00:00
call_with_arch ( before_test_prepared , env = virtualenv_env , shell = True )
# install the wheel
2021-04-30 17:56:34 -04:00
call_with_arch (
2021-05-03 11:45:43 -04:00
[ "pip" , "install" , f " { repaired_wheel }{ 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
if options . test_requires :
2021-04-30 17:56:34 -04:00
call_with_arch (
2021-05-03 11:45:43 -04:00
[ "pip" , "install" ] + 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 (
options . test_command ,
2021-05-03 11:45:43 -04:00
project = Path ( "." ) . resolve (),
2021-04-30 17:56:34 -04:00
package = options . package_dir . resolve (),
)
call_with_arch (
test_command_prepared ,
2021-05-03 11:45:43 -04:00
cwd = os . environ [ "HOME" ],
2021-04-30 17:56:34 -04:00
env = virtualenv_env ,
shell = True ,
2021-01-05 15:58:53 +00:00
)
# clean up
shutil . rmtree ( venv_dir )
2020-11-13 16:30:27 +00:00
# we're all done here; move it to output (overwrite existing)
shutil . move ( str ( repaired_wheel ), options . output_dir )
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 )