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
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
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
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
2024-06-10 15:36:20 +01:00
from . import errors
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 ,
2023-08-26 19:37:29 +01:00
BuildFrontendConfig ,
BuildFrontendName ,
2021-01-06 13:50:58 -05:00
BuildSelector ,
2022-01-05 02:59:24 +01:00
call ,
2024-06-09 15:45:31 -04:00
combine_constraints ,
2021-01-06 13:50:58 -05:00
download ,
2024-05-28 05:31:36 -07:00
extract_zip ,
2022-06-19 16:34:28 +02:00
find_compatible_wheel ,
2024-06-09 15:45:31 -04:00
find_uv ,
2021-01-06 13:50:58 -05:00
get_build_verbosity_extra_flags ,
2021-06-23 10:47:18 -04:00
get_pip_version ,
2024-06-07 17:37:20 +02:00
move_file ,
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
2024-05-17 21:44:08 +02:00
def get_nuget_args (
version : str , arch : str , free_threaded : bool , 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 ]
2024-05-17 21:44:08 +02:00
if free_threaded :
package_name = f " { package_name } -freethreaded"
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
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
2024-05-19 14:01:12 +02:00
def install_cpython ( configuration : PythonConfiguration , arch : str | None = None ) -> Path :
version = configuration . version
free_threaded = "t-" in configuration . identifier
if arch is None :
arch = configuration . arch
2022-01-08 11:30:48 +01:00
base_output_dir = CIBW_CACHE_PATH / "nuget-cpython"
2024-05-17 21:44:08 +02:00
nuget_args = get_nuget_args ( version , arch , free_threaded , base_output_dir )
2022-01-08 11:30:48 +01:00
installation_path = base_output_dir / ( nuget_args [ 0 ] + "." + version ) / "tools"
2024-05-17 21:44:08 +02:00
free_threaded_str = "-freethreaded" if free_threaded else ""
with FileLock ( str ( base_output_dir ) + f "- { version }{ free_threaded_str } - { arch } .lock" ):
2022-01-08 11:30:48 +01:00
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 ,
2024-06-13 02:18:09 -04:00
) -> tuple [ Path , 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 ()
2024-05-19 14:01:12 +02:00
base_python = install_cpython ( python_configuration )
2022-06-17 16:39:56 +01:00
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.
2024-05-19 14:01:12 +02:00
python_libs_base = base_python . parent / "libs"
2022-06-17 16:39:56 +01:00
log . step ( f "Installing native Python { native_arch } for cross-compilation..." )
2024-05-19 14:01:12 +02:00
base_python = install_cpython ( python_configuration , arch = native_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
2024-06-09 15:45:31 -04:00
use_uv = build_frontend == "build[uv]" and Version ( python_configuration . version ) >= Version (
"3.8"
)
uv_path = find_uv ()
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 (
2024-06-09 15:45:31 -04:00
python_configuration . version ,
base_python ,
venv_path ,
dependency_constraint_flags ,
use_uv = use_uv ,
2024-05-20 08:21:42 +02:00
)
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-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'
2024-06-09 15:45:31 -04:00
if not use_uv :
call (
"python" ,
"-m" ,
"pip" ,
"install" ,
"--upgrade" ,
"pip" ,
* dependency_constraint_flags ,
env = env ,
cwd = venv_path ,
)
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" ):
2024-06-10 15:36:20 +01:00
msg = "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."
raise errors . FatalError ( msg )
2021-12-13 09:39:41 -05:00
# check what pip version we're on
2024-06-09 15:45:31 -04:00
if not use_uv :
assert ( venv_path / "Scripts" / "pip.exe" ) . exists ()
where_pip = call ( "where" , "pip" , env = env , capture_stdout = True ) . splitlines ()[ 0 ] . strip ()
if where_pip . strip () != str ( venv_path / "Scripts" / "pip.exe" ):
2024-06-10 15:36:20 +01:00
msg = "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."
raise errors . FatalError ( msg )
2020-04-05 16:03:35 +02:00
2024-06-09 15:45:31 -04: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 ,
)
2024-06-09 15:45:31 -04:00
elif build_frontend == "build[uv]" :
assert uv_path is not None
call (
uv_path ,
"pip" ,
"install" ,
"--upgrade" ,
"build[virtualenv]" ,
* dependency_constraint_flags ,
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
2024-06-13 02:18:09 -04:00
return base_python , env
2020-06-14 11:25:14 -04: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 )
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" )
2024-06-09 15:45:31 -04:00
use_uv = build_frontend . name == "build[uv]" and Version ( config . version ) >= Version (
"3.8"
)
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
2024-06-13 02:18:09 -04:00
base_python , 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-06-09 15:45:31 -04:00
if not use_uv :
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 :
# 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
2024-05-10 21:33:56 +02:00
build_env = env . copy ()
2024-06-09 15:45:31 -04:00
if not use_uv :
build_env [ "VIRTUALENV_PIP" ] = pip_version
2024-05-10 21:33:56 +02:00
if build_options . dependency_constraints :
constraints_path = build_options . dependency_constraints . get_for_python_version (
config . version
)
2024-06-09 15:45:31 -04:00
combine_constraints ( build_env , constraints_path , identifier_tmp_dir )
2024-05-10 21:33:56 +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 ,
2024-05-10 21:33:56 +02:00
env = build_env ,
2021-06-23 10:47:18 -04:00
)
2024-06-09 15:45:31 -04:00
elif build_frontend . name == "build" or build_frontend . name == "build[uv]" :
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 )
2024-07-01 20:15:09 -04:00
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags :
2024-06-09 15:45:31 -04:00
extra_flags . append ( "--installer=uv" )
2024-05-10 21:33:56 +02:00
call (
"python" ,
"-m" ,
"build" ,
build_options . package_dir ,
"--wheel" ,
f "--outdir= { built_wheel_dir } " ,
* extra_flags ,
env = build_env ,
)
2022-04-18 14:24:53 +02:00
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" ):
2024-07-07 09:47:03 +02:00
raise errors . 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 }:
2024-07-07 09:47:03 +02:00
raise errors . AlreadyBuiltWheelError ( repaired_wheel . name )
2022-06-19 18:03:05 +02:00
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.
2024-06-09 15:45:31 -04:00
if not use_uv :
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
2024-06-09 15:45:31 -04:00
if use_uv :
2024-06-13 02:18:09 -04:00
call ( "uv" , "venv" , venv_dir , f "--python= { base_python } " , env = env )
2024-06-09 15:45:31 -04:00
else :
# 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 ( "python" , "-m" , "virtualenv" , * venv_args , 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
]
)
2024-05-29 02:20:02 -04:00
virtualenv_env [ "VIRTUAL_ENV" ] = str ( venv_dir )
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 )
2024-06-09 15:45:31 -04:00
pip = [ "uv" , "pip" ] if use_uv else [ "pip" ]
2020-11-13 16:30:27 +00:00
# install the wheel
2021-04-30 17:56:34 -04:00
call (
2024-06-09 15:45:31 -04:00
* pip ,
2022-01-05 02:59:24 +01:00
"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 :
2024-06-09 15:45:31 -04: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 :
2024-06-07 17:37:20 +02:00
output_wheel = build_options . output_dir . joinpath ( repaired_wheel . name )
moved_wheel = move_file ( repaired_wheel , output_wheel )
if moved_wheel != output_wheel . resolve ():
log . warning (
" {repaired_wheel} was moved to {moved_wheel} instead of {output_wheel} "
)
built_wheels . append ( output_wheel )
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 :
2024-06-10 15:36:20 +01:00
msg = f "Command { error . cmd } failed with code { error . returncode } . { error . stdout or '' } "
raise errors . FatalError ( msg ) from error