2022-07-14 13:36:57 +02:00
from __future__ import annotations
2019-11-12 23:51:27 +00:00
import os
2022-06-17 16:39:56 +01:00
import platform as platform_module
2019-11-12 23:51:27 +00:00
import shutil
import subprocess
2020-02-19 17:56:11 +01:00
import sys
2022-08-01 20:23:30 +01:00
import textwrap
2023-04-18 13:06:17 -04:00
from collections.abc import MutableMapping , Sequence , Set
2023-04-17 13:06:14 -05:00
from contextlib import suppress
2022-04-28 09:19:27 -04:00
from dataclasses import dataclass
2022-01-08 11:30:48 +01:00
from functools import lru_cache
2020-06-15 01:53:31 +02:00
from pathlib import Path
2019-11-07 00:55:58 +01:00
from zipfile import ZipFile
2020-11-13 16:30:27 +00:00
2022-01-08 11:30:48 +01:00
from filelock import FileLock
2021-11-24 10:50:49 -08:00
from packaging.version import Version
2023-04-18 23:05:34 -04:00
from ._compat.typing import assert_never
2021-01-22 09:33:22 -05: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 ,
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
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 ,
2022-08-31 21:24:47 +01:00
unwrap ,
2022-01-08 11:30:48 +01:00
virtualenv ,
2021-01-06 13:50:58 -05:00
)
2017-04-10 21:42:12 +01:00
2021-07-09 09:16:20 -04:00
2022-07-14 13:36:57 +02:00
def get_nuget_args ( version : str , arch : str , output_directory : Path ) -> list [ str ]:
2022-06-17 17:11:09 +01:00
package_name = {
"32" : "pythonx86" ,
"64" : "python" ,
"ARM64" : "pythonarm64" ,
# Aliases for platform.machine() return values
"x86" : "pythonx86" ,
"AMD64" : "python" ,
}[ arch ]
2021-04-30 22:41:19 +01:00
return [
2022-06-17 17:11:09 +01:00
package_name ,
2021-05-03 11:45:43 -04:00
"-Version" ,
2021-04-30 17:56:34 -04:00
version ,
2021-05-03 11:45:43 -04:00
"-FallbackSource" ,
"https://api.nuget.org/v3/index.json" ,
"-OutputDirectory" ,
2022-01-08 11:30:48 +01:00
str ( output_directory ),
2021-04-30 22:41:19 +01:00
]
2019-10-23 22:53:10 +02:00
2019-11-12 23:51:27 +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
arch : str
identifier : str
2022-07-14 13:36:57 +02:00
url : str | None = None
2020-04-08 00:16:25 +02:00
2021-01-09 15:40:40 -05:00
def get_python_configurations (
2021-04-30 17:56:34 -04:00
build_selector : BuildSelector ,
2023-04-06 11:40:25 -07:00
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 ( "windows" )
2021-01-09 15:40:40 -05:00
python_configurations = [ PythonConfiguration ( ** item ) for item in full_python_configs ]
2021-11-24 10:50:49 -08:00
map_arch = { "32" : Architecture . x86 , "64" : Architecture . AMD64 , "ARM64" : Architecture . ARM64 }
2021-01-01 16:53:45 -05:00
2019-11-12 23:51:27 +00:00
# skip builds as required
2021-01-01 16:53:45 -05:00
python_configurations = [
2021-04-30 17:56:34 -04:00
c
for c in python_configurations
2021-01-01 16:53:45 -05:00
if build_selector ( c . identifier ) and map_arch [ c . arch ] in architectures
]
2019-09-28 19:24:16 +02:00
2019-10-23 22:53:10 +02:00
return python_configurations
2019-04-27 10:20:37 +01:00
2019-04-20 18:52:01 +02:00
2020-06-15 01:53:31 +02:00
def extract_zip ( zip_src : Path , dest : Path ) -> None :
2021-12-27 09:26:33 +01:00
with ZipFile ( zip_src ) as zip_ :
zip_ . extractall ( dest )
2020-02-11 14:44:53 +01:00
2022-01-08 11:30:48 +01:00
@lru_cache ( maxsize = None )
def _ensure_nuget () -> Path :
nuget = CIBW_CACHE_PATH / "nuget.exe"
with FileLock ( str ( nuget ) + ".lock" ):
if not nuget . exists ():
download ( "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" , nuget )
return nuget
2020-02-11 14:44:53 +01:00
2022-01-08 11:30:48 +01:00
def install_cpython ( version : str , arch : str ) -> Path :
base_output_dir = CIBW_CACHE_PATH / "nuget-cpython"
nuget_args = get_nuget_args ( version , arch , base_output_dir )
installation_path = base_output_dir / ( nuget_args [ 0 ] + "." + version ) / "tools"
with FileLock ( str ( base_output_dir ) + f "- { version } - { arch } .lock" ):
if not installation_path . exists ():
nuget = _ensure_nuget ()
call ( nuget , "install" , * nuget_args )
return installation_path / "python.exe"
def install_pypy ( tmp : Path , arch : str , url : str ) -> Path :
2023-01-30 15:53:44 -05:00
assert arch == "64"
assert "win64" in url
2020-02-11 14:44:53 +01:00
# Inside the PyPy zip file is a directory with the same name
2021-05-03 11:45:43 -04:00
zip_filename = url . rsplit ( "/" , 1 )[ - 1 ]
2020-06-15 01:53:31 +02:00
extension = ".zip"
assert zip_filename . endswith ( extension )
2022-01-08 11:30:48 +01:00
installation_path = CIBW_CACHE_PATH / zip_filename [: - len ( extension )]
with FileLock ( str ( installation_path ) + ".lock" ):
if not installation_path . exists ():
pypy_zip = tmp / zip_filename
download ( url , pypy_zip )
# Extract to the parent directory because the zip file still contains a directory
extract_zip ( pypy_zip , installation_path . parent )
return installation_path / "python.exe"
2020-02-11 14:44:53 +01:00
2022-06-17 16:39:56 +01:00
def setup_setuptools_cross_compile (
2022-09-01 22:15:57 +01:00
tmp : Path ,
2022-06-17 16:39:56 +01:00
python_configuration : PythonConfiguration ,
python_libs_base : Path ,
2023-04-18 13:06:17 -04:00
env : MutableMapping [ str , str ],
2022-06-17 16:44:17 +01:00
) -> None :
2022-09-01 22:15:57 +01:00
distutils_cfg = tmp / "extra-setup.cfg"
2022-09-26 17:26:16 +01:00
env [ "DIST_EXTRA_CONFIG" ] = str ( distutils_cfg )
log . notice ( f "Setting DIST_EXTRA_CONFIG= { distutils_cfg } for cross-compilation" )
2022-06-17 16:39:56 +01:00
# Ensure our additional import libraries are made available, and explicitly
# set the platform name
map_plat = { "32" : "win32" , "64" : "win-amd64" , "ARM64" : "win-arm64" }
plat_name = map_plat [ python_configuration . arch ]
2022-08-01 20:23:30 +01:00
# (This file must be default/locale encoding, so we can't pass 'encoding')
distutils_cfg . write_text (
textwrap . dedent (
f """ \
[build]
plat_name= { plat_name }
[build_ext]
library_dirs= { python_libs_base }
plat_name= { plat_name }
[bdist_wheel]
plat_name= { plat_name }
"""
)
)
2022-06-17 16:39:56 +01:00
# setuptools builds require explicit override of PYD extension
# This is because it always gets the extension from the running
# interpreter, and has no logic to construct it. Currently, CPython's
# extensions follow our identifiers, but if they ever diverge in the
# future, we will need to store new data
2022-08-01 19:26:31 +00:00
log . notice (
f "Setting SETUPTOOLS_EXT_SUFFIX=. { python_configuration . identifier } .pyd for cross-compilation"
)
2022-06-17 16:39:56 +01:00
env [ "SETUPTOOLS_EXT_SUFFIX" ] = f ". { python_configuration . identifier } .pyd"
# Cross-compilation requires fixes that only exist in setuptools's copy of
# distutils, so ensure that it is activated
# Since not all projects can handle the newer distutils, display a warning
# to help them figure out what may have gone wrong if this breaks for them
2022-08-01 20:23:30 +01:00
log . notice ( "Setting SETUPTOOLS_USE_DISTUTILS=local as it is required for cross-compilation" )
2022-06-17 16:39:56 +01:00
env [ "SETUPTOOLS_USE_DISTUTILS" ] = "local"
2023-01-30 15:53:44 -05:00
# These cross-compile setup functions have the same signature by design
2022-08-01 20:11:47 +01:00
def setup_rust_cross_compile (
2023-01-30 15:53:44 -05:00
tmp : Path , # noqa: ARG001
2022-08-01 20:11:47 +01:00
python_configuration : PythonConfiguration ,
2023-01-30 15:53:44 -05:00
python_libs_base : Path , # noqa: ARG001
2023-04-18 13:06:17 -04:00
env : MutableMapping [ str , str ],
2022-08-01 20:11:47 +01:00
) -> None :
# Assume that MSVC will be used, because we already know that we are
# cross-compiling. MinGW users can set CARGO_BUILD_TARGET themselves
# and we will respect the existing value.
cargo_target = {
"64" : "x86_64-pc-windows-msvc" ,
"32" : "i686-pc-windows-msvc" ,
"ARM64" : "aarch64-pc-windows-msvc" ,
} . get ( python_configuration . arch )
# CARGO_BUILD_TARGET is the variable used by Cargo and setuptools_rust
if env . get ( "CARGO_BUILD_TARGET" ):
if env [ "CARGO_BUILD_TARGET" ] != cargo_target :
2022-08-01 20:23:30 +01:00
log . notice ( "Not overriding CARGO_BUILD_TARGET as it has already been set" )
2022-08-01 20:11:47 +01:00
# No message if it was set to what we were planning to set it to
elif cargo_target :
2022-08-01 20:29:51 +01:00
log . notice ( f "Setting CARGO_BUILD_TARGET= { cargo_target } for cross-compilation" )
2022-08-01 20:11:47 +01:00
env [ "CARGO_BUILD_TARGET" ] = cargo_target
else :
log . warning (
2022-08-01 20:29:51 +01:00
f "Unable to configure Rust cross-compilation for architecture { python_configuration . arch } "
2022-08-01 20:11:47 +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 ]
2022-06-17 16:39:56 +01:00
python_libs_base = None
2021-05-03 11:45:43 -04:00
log . step ( f "Installing Python { implementation_id } ..." )
if implementation_id . startswith ( "cp" ):
2022-06-17 16:39:56 +01:00
native_arch = platform_module . machine ()
if python_configuration . arch == "ARM64" != native_arch :
# To cross-compile for ARM64, we need a native CPython to run the
# build, and a copy of the ARM64 import libraries ('.\libs\*.lib')
# for any extension modules.
2022-06-17 15:42:07 +00:00
python_libs_base = install_cpython (
python_configuration . version , python_configuration . arch
)
2022-06-17 16:39:56 +01:00
python_libs_base = python_libs_base . parent / "libs"
log . step ( f "Installing native Python { native_arch } for cross-compilation..." )
base_python = install_cpython ( python_configuration . version , native_arch )
else :
base_python = install_cpython ( python_configuration . version , python_configuration . arch )
2021-05-03 11:45:43 -04:00
elif implementation_id . startswith ( "pp" ):
2020-04-10 20:24:52 +02:00
assert python_configuration . url is not None
2022-01-08 11:30:48 +01:00
base_python = install_pypy ( tmp , python_configuration . arch , 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-05 16:03:35 +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 )
2020-11-13 16:30:27 +00:00
2022-01-08 11:30:48 +01:00
# set up environment variables for run_with_env
2021-05-03 11:45:43 -04:00
env [ "PYTHON_VERSION" ] = python_configuration . version
env [ "PYTHON_ARCH" ] = python_configuration . arch
env [ "PIP_DISABLE_PIP_VERSION_CHECK" ] = "1"
2021-01-24 15:08:46 +00:00
2021-11-24 10:50:49 -08:00
# pip older than 21.3 builds executables such as pip.exe for x64 platform.
# The first re-install of pip updates pip module but builds pip.exe using
# the old pip which still generates x64 executable. But the second
# re-install uses updated pip and correctly builds pip.exe for the target.
# This can be removed once ARM64 Pythons (currently 3.9 and 3.10) bundle
# pip versions newer than 21.3.
if python_configuration . arch == "ARM64" and Version ( get_pip_version ( env )) < Version ( "21.3" ):
call (
2022-01-05 02:59:24 +01:00
"python" ,
"-m" ,
"pip" ,
"install" ,
"--force-reinstall" ,
"--upgrade" ,
"pip" ,
* dependency_constraint_flags ,
2021-11-24 10:50:49 -08:00
env = env ,
2022-01-08 11:30:48 +01:00
cwd = venv_path ,
2021-11-24 10:50:49 -08: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.exe'
2021-05-13 20:32:38 +02: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-13 20:32:38 +02:00
env = env ,
2022-01-08 11:30:48 +01:00
cwd = venv_path ,
2021-05-13 20:32:38 +02:00
)
2021-05-08 18:03:35 +01:00
2021-12-13 09:39:41 -05:00
# update env with results from CIBW_ENVIRONMENT
env = environment . as_dictionary ( prev_environment = env )
# check what Python version we're on
2022-01-05 02:59:24 +01:00
call ( "where" , "python" , env = env )
call ( "python" , "--version" , env = env )
call ( "python" , "-c" , " \" import struct; print(struct.calcsize('P') * 8) \" " , env = env )
where_python = call ( "where" , "python" , env = env , capture_stdout = True ) . splitlines ()[ 0 ] . strip ()
2022-01-08 11:30:48 +01:00
if where_python != str ( venv_path / "Scripts" / "python.exe" ):
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 )
# check what pip version we're on
2022-01-08 11:30:48 +01:00
assert ( venv_path / "Scripts" / "pip.exe" ) . exists ()
2022-01-05 02:59:24 +01:00
where_pip = call ( "where" , "pip" , env = env , capture_stdout = True ) . splitlines ()[ 0 ] . strip ()
2022-01-08 11:30:48 +01:00
if where_pip . strip () != str ( venv_path / "Scripts" / "pip.exe" ):
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
2022-01-05 02:59:24 +01:00
call ( "pip" , "--version" , env = env )
2021-06-23 10:47:18 -04:00
2022-01-08 11:30:48 +01:00
log . step ( "Installing build tools..." )
2024-01-20 18:42:02 +00:00
if build_frontend == "build" :
2021-06-23 10:47:18 -04:00
call (
2022-01-05 02:59:24 +01:00
"pip" ,
"install" ,
"--upgrade" ,
"build[virtualenv]" ,
* dependency_constraint_flags ,
2021-06-23 10:47:18 -04:00
env = env ,
)
2020-04-05 16:03:35 +02:00
2022-06-17 16:39:56 +01:00
if python_libs_base :
# Set up the environment for various backends to enable cross-compilation
2022-09-01 22:15:57 +01:00
setup_setuptools_cross_compile ( tmp , python_configuration , python_libs_base , env )
setup_rust_cross_compile ( tmp , python_configuration , python_libs_base , env )
2022-06-17 16:39:56 +01:00
2020-06-14 11:25:14 -04:00
return env
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 )
before_all_prepared = prepare_command (
before_all_options . before_all , project = "." , package = options . globals . package_dir
)
shell ( before_all_prepared , env = env )
2019-04-27 10:20:37 +01: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 )
2020-04-06 16:55:15 +02: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-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
]
2017-03-19 21:27:29 +00:00
2020-11-13 16:30:27 +00:00
# install Python
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
)
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 :
# run the before_build command
if build_options . before_build :
log . step ( "Running before_build..." )
before_build_prepared = prepare_command (
2022-09-01 22:13:08 +01:00
build_options . before_build ,
project = "." ,
package = options . globals . package_dir ,
2021-07-09 09:16:20 -04:00
)
2022-04-18 14:24:53 +02:00
shell ( before_build_prepared , env = env )
2021-07-09 09:16:20 -04:00
2022-04-18 14:24:53 +02:00
log . step ( "Building wheel..." )
built_wheel_dir . mkdir ()
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
2022-04-18 14:24:53 +02:00
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
2021-07-09 09:16:20 -04:00
call (
2022-01-05 02:59:24 +01:00
"python" ,
"-m" ,
2022-04-18 14:24:53 +02:00
"pip" ,
"wheel" ,
options . globals . 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 ,
2021-06-23 10:47:18 -04: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
build_env = env . copy ()
if build_options . dependency_constraints :
constraints_path = (
build_options . dependency_constraints . get_for_python_version (
config . version
)
)
# Bug in pip <= 21.1.3 - we can't have a space in the
# constraints file, and pip doesn't support drive letters
# in uhi. After probably pip 21.2, we can use uri. For
# now, use a temporary file.
if " " in str ( constraints_path ):
assert " " not in str ( identifier_tmp_dir )
tmp_file = identifier_tmp_dir / "constraints.txt"
tmp_file . write_bytes ( constraints_path . read_bytes ())
constraints_path = tmp_file
2019-11-12 23:34:59 +00:00
2022-04-18 14:24:53 +02:00
build_env [ "PIP_CONSTRAINT" ] = str ( constraints_path )
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 ,
)
else :
2023-04-07 15:51:12 -07:00
assert_never ( build_frontend )
2020-07-20 15:35:51 +01:00
2022-04-18 14:24:53 +02:00
built_wheel = next ( built_wheel_dir . glob ( "*.whl" ))
2020-07-20 15:35:51 +01:00
2022-04-18 14:24:53 +02:00
# repair the wheel
repaired_wheel_dir . mkdir ()
2020-07-20 15:35:51 +01:00
2022-04-18 14:24:53 +02:00
if built_wheel . name . endswith ( "none-any.whl" ):
raise NonPlatformWheelError ()
2017-03-19 21:27:29 +00:00
2022-04-18 14:24:53 +02:00
if build_options . repair_command :
log . step ( "Repairing wheel..." )
repair_command_prepared = prepare_command (
2022-09-01 22:13:08 +01:00
build_options . repair_command ,
wheel = built_wheel ,
dest_dir = repaired_wheel_dir ,
2022-04-18 14:24:53 +02:00
)
shell ( repair_command_prepared , env = env )
else :
shutil . move ( str ( built_wheel ), repaired_wheel_dir )
repaired_wheel = next ( repaired_wheel_dir . glob ( "*.whl" ))
2020-03-01 11:09:22 +00:00
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-12-22 11:22:13 -05:00
test_selected = options . globals . test_selector ( config . identifier )
if test_selected and config . arch == "ARM64" != platform_module . machine ():
log . warning (
unwrap (
"""
2022-08-31 21:08:15 +01:00
While arm64 wheels can be built on other platforms, they cannot
be tested. An arm64 runner is required. To silence this warning,
2023-11-18 12:37:50 +02:00
set `CIBW_TEST_SKIP: "*-win_arm64"`.
2022-08-31 21:08:15 +01:00
"""
)
2022-12-22 11:22:13 -05:00
)
# skip this test
elif test_selected and build_options . test_command :
2021-05-03 11:45:43 -04:00
log . step ( "Testing wheel..." )
2020-11-13 16:30:27 +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:47:00 +01:00
2020-11-13 16:30:27 +00:00
# Use --no-download to ensure determinism by using seed libraries
# built into virtualenv
2022-01-05 02:59:24 +01:00
call ( "python" , "-m" , "virtualenv" , "--no-download" , venv_dir , env = env )
2019-10-12 10:47:00 +01:00
2020-11-13 16:30:27 +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 / "Scripts" ),
virtualenv_env [ "PATH" ],
2021-04-30 17:56:34 -04:00
]
)
2019-10-12 10:47:00 +01:00
2020-11-13 16:30:27 +00:00
# check that we are using the Python from the virtual environment
2022-01-05 02:59:24 +01:00
call ( "where" , "python" , env = virtualenv_env )
2020-11-13 16:30:27 +00:00
2021-10-12 02:05:47 +01:00
if build_options . before_test :
2020-11-13 16:30:27 +00:00
before_test_prepared = prepare_command (
2021-10-12 02:05:47 +01:00
build_options . before_test ,
2021-05-03 11:45:43 -04:00
project = "." ,
2021-10-12 02:05:47 +01:00
package = build_options . package_dir ,
2020-11-13 16:30:27 +00:00
)
shell ( before_test_prepared , env = virtualenv_env )
# install the wheel
2021-04-30 17:56:34 -04:00
call (
2022-01-05 02:59:24 +01:00
"pip" ,
"install" ,
str ( repaired_wheel ) + build_options . test_extras ,
2021-04-30 17:56:34 -04:00
env = virtualenv_env ,
)
2020-11-13 16:30:27 +00:00
# test the wheel
2021-10-12 02:05:47 +01:00
if build_options . test_requires :
2022-01-05 02:59:24 +01:00
call ( "pip" , "install" , * build_options . test_requires , env = virtualenv_env )
2020-11-13 16:30:27 +00:00
2022-11-06 00:01:30 +00:00
# run the tests from a temp dir, with an absolute path in the command
2020-11-13 16:30:27 +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 = options . globals . package_dir . resolve (),
2023-06-23 08:29:58 -04:00
wheel = repaired_wheel ,
2020-04-09 16:19:36 +01:00
)
2022-11-06 00:01:30 +00:00
test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd . mkdir ()
( test_cwd / "test_fail.py" ) . write_text ( test_fail_cwd_file . read_text ())
shell ( test_command_prepared , cwd = test_cwd , env = virtualenv_env )
2020-01-07 00:17:03 +01:00
2020-11-13 16:30:27 +00:00
# we're all done here; move it to output (remove if already exists)
2022-06-19 16:34:28 +02:00
if compatible_wheel is None :
2023-04-17 13:06:14 -05:00
with suppress ( FileNotFoundError ):
( build_options . output_dir / repaired_wheel . name ) . unlink ()
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
# (we ignore errors because occasionally Windows fails to unlink a file and we
# don't want to abort a build because of that)
shutil . rmtree ( identifier_tmp_dir , ignore_errors = True )
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 )