From 5eb5ea10f5f810c32d558f3d43bde609de64205a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 9 Jan 2021 15:40:40 -0500 Subject: [PATCH 01/27] refactor: identifiers in a file (#511) * refactor: identifiers in a file * chore: more typing * refactor: PlatStr -> PlatformName * refactor: load in file, use importlib.resources * refactor: change toml structure * refactor: use common Traversable * fix: ensure reasonable version of importlib_resources * style: fix extra line --- cibuildwheel/__main__.py | 15 ++++- cibuildwheel/linux.py | 47 +++------------ cibuildwheel/macos.py | 24 +++----- cibuildwheel/resources/build-platforms.toml | 67 +++++++++++++++++++++ cibuildwheel/typing.py | 17 +++++- cibuildwheel/util.py | 32 ++++++---- cibuildwheel/windows.py | 37 ++++-------- setup.cfg | 5 ++ unit_test/build_ids_test.py | 28 +++++++++ 9 files changed, 180 insertions(+), 92 deletions(-) create mode 100644 cibuildwheel/resources/build-platforms.toml create mode 100644 unit_test/build_ids_test.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 8f76c6a3..5ca9c104 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -12,6 +12,7 @@ import cibuildwheel.linux import cibuildwheel.macos import cibuildwheel.windows from cibuildwheel.environment import EnvironmentParseError, parse_environment +from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never from cibuildwheel.util import ( Architecture, BuildOptions, @@ -46,6 +47,8 @@ def get_option_from_environment(option_name: str, platform: Optional[str] = None def main() -> None: + platform: PlatformName + parser = argparse.ArgumentParser( description='Build wheels for all the platforms.', epilog=''' @@ -120,6 +123,10 @@ def main() -> None: file=sys.stderr) exit(2) + if platform not in PLATFORMS: + print(f'cibuildwheel: Unsupported platform: {platform}', file=sys.stderr) + exit(2) + package_dir = Path(args.package_dir) output_dir = Path(args.output_dir) @@ -127,8 +134,10 @@ def main() -> None: repair_command_default = 'auditwheel repair -w {dest_dir} {wheel}' elif platform == 'macos': repair_command_default = 'delocate-listdeps {wheel} && delocate-wheel --require-archs x86_64 -w {dest_dir} {wheel}' - else: + elif platform == 'windows': repair_command_default = '' + else: + assert_never(platform) build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '') environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='') @@ -248,8 +257,7 @@ def main() -> None: elif platform == 'macos': cibuildwheel.macos.build(build_options) else: - print(f'cibuildwheel: Unsupported platform: {platform}', file=sys.stderr) - exit(2) + assert_never(platform) def detect_obsolete_options() -> None: @@ -302,6 +310,7 @@ def print_preamble(platform: str, build_options: BuildOptions) -> None: def print_build_identifiers( platform: str, build_selector: BuildSelector, architectures: Set[Architecture] ) -> None: + python_configurations: List[Any] = [] if platform == 'linux': python_configurations = cibuildwheel.linux.get_python_configurations(build_selector, architectures) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 7da89aee..6573aa82 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -15,6 +15,8 @@ from .util import ( allowed_architectures_check, get_build_verbosity_extra_flags, prepare_command, + read_python_configs, + resources_dir, ) @@ -29,42 +31,13 @@ class PythonConfiguration(NamedTuple): def get_python_configurations( - build_selector: BuildSelector, architectures: Set[Architecture] + build_selector: BuildSelector, + architectures: Set[Architecture] ) -> List[PythonConfiguration]: - python_configurations = [ - PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27m'), - PythonConfiguration(version='2.7', identifier='cp27-manylinux_x86_64', path_str='/opt/python/cp27-cp27mu'), - PythonConfiguration(version='3.5', identifier='cp35-manylinux_x86_64', path_str='/opt/python/cp35-cp35m'), - PythonConfiguration(version='3.6', identifier='cp36-manylinux_x86_64', path_str='/opt/python/cp36-cp36m'), - PythonConfiguration(version='3.7', identifier='cp37-manylinux_x86_64', path_str='/opt/python/cp37-cp37m'), - PythonConfiguration(version='3.8', identifier='cp38-manylinux_x86_64', path_str='/opt/python/cp38-cp38'), - PythonConfiguration(version='3.9', identifier='cp39-manylinux_x86_64', path_str='/opt/python/cp39-cp39'), - PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27m'), - PythonConfiguration(version='2.7', identifier='cp27-manylinux_i686', path_str='/opt/python/cp27-cp27mu'), - PythonConfiguration(version='3.5', identifier='cp35-manylinux_i686', path_str='/opt/python/cp35-cp35m'), - PythonConfiguration(version='3.6', identifier='cp36-manylinux_i686', path_str='/opt/python/cp36-cp36m'), - PythonConfiguration(version='3.7', identifier='cp37-manylinux_i686', path_str='/opt/python/cp37-cp37m'), - PythonConfiguration(version='3.8', identifier='cp38-manylinux_i686', path_str='/opt/python/cp38-cp38'), - PythonConfiguration(version='3.9', identifier='cp39-manylinux_i686', path_str='/opt/python/cp39-cp39'), - PythonConfiguration(version='2.7', identifier='pp27-manylinux_x86_64', path_str='/opt/python/pp27-pypy_73'), - PythonConfiguration(version='3.6', identifier='pp36-manylinux_x86_64', path_str='/opt/python/pp36-pypy36_pp73'), - PythonConfiguration(version='3.7', identifier='pp37-manylinux_x86_64', path_str='/opt/python/pp37-pypy37_pp73'), - PythonConfiguration(version='3.5', identifier='cp35-manylinux_aarch64', path_str='/opt/python/cp35-cp35m'), - PythonConfiguration(version='3.6', identifier='cp36-manylinux_aarch64', path_str='/opt/python/cp36-cp36m'), - PythonConfiguration(version='3.7', identifier='cp37-manylinux_aarch64', path_str='/opt/python/cp37-cp37m'), - PythonConfiguration(version='3.8', identifier='cp38-manylinux_aarch64', path_str='/opt/python/cp38-cp38'), - PythonConfiguration(version='3.9', identifier='cp39-manylinux_aarch64', path_str='/opt/python/cp39-cp39'), - PythonConfiguration(version='3.5', identifier='cp35-manylinux_ppc64le', path_str='/opt/python/cp35-cp35m'), - PythonConfiguration(version='3.6', identifier='cp36-manylinux_ppc64le', path_str='/opt/python/cp36-cp36m'), - PythonConfiguration(version='3.7', identifier='cp37-manylinux_ppc64le', path_str='/opt/python/cp37-cp37m'), - PythonConfiguration(version='3.8', identifier='cp38-manylinux_ppc64le', path_str='/opt/python/cp38-cp38'), - PythonConfiguration(version='3.9', identifier='cp39-manylinux_ppc64le', path_str='/opt/python/cp39-cp39'), - PythonConfiguration(version='3.5', identifier='cp35-manylinux_s390x', path_str='/opt/python/cp35-cp35m'), - PythonConfiguration(version='3.6', identifier='cp36-manylinux_s390x', path_str='/opt/python/cp36-cp36m'), - PythonConfiguration(version='3.7', identifier='cp37-manylinux_s390x', path_str='/opt/python/cp37-cp37m'), - PythonConfiguration(version='3.8', identifier='cp38-manylinux_s390x', path_str='/opt/python/cp38-cp38'), - PythonConfiguration(version='3.9', identifier='cp39-manylinux_s390x', path_str='/opt/python/cp39-cp39'), - ] + + full_python_configs = read_python_configs('linux') + + python_configurations = [PythonConfiguration(**item) for item in full_python_configs] # return all configurations whose arch is in our `architectures` set, # and match the build/skip rules @@ -76,7 +49,7 @@ def get_python_configurations( def build(options: BuildOptions) -> None: - allowed_architectures_check("linux", options) + allowed_architectures_check('linux', options) try: subprocess.check_output(['docker', '--version']) @@ -136,7 +109,7 @@ def build(options: BuildOptions) -> None: if config.identifier.startswith("pp"): # Patch PyPy to make sure headers get installed into a venv patch_version = '_27' if config.version == '2.7' else '' - patch_path = Path(__file__).absolute().parent / 'resources' / f'pypy_venv{patch_version}.patch' + patch_path = resources_dir / f'pypy_venv{patch_version}.patch' patch_docker_path = PurePath('/pypy_venv.patch') docker.copy_into(patch_path, patch_docker_path) try: diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 519c8bc1..de9cd15e 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -20,6 +20,8 @@ from .util import ( get_pip_script, install_certifi_script, prepare_command, + read_python_configs, + resources_dir, ) @@ -39,20 +41,12 @@ class PythonConfiguration(NamedTuple): url: str -def get_python_configurations(build_selector: BuildSelector) -> List[PythonConfiguration]: - python_configurations = [ - # CPython - PythonConfiguration(version='2.7', identifier='cp27-macosx_x86_64', url='https://www.python.org/ftp/python/2.7.18/python-2.7.18-macosx10.9.pkg'), - PythonConfiguration(version='3.5', identifier='cp35-macosx_x86_64', url='https://www.python.org/ftp/python/3.5.4/python-3.5.4-macosx10.6.pkg'), - PythonConfiguration(version='3.6', identifier='cp36-macosx_x86_64', url='https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg'), - PythonConfiguration(version='3.7', identifier='cp37-macosx_x86_64', url='https://www.python.org/ftp/python/3.7.9/python-3.7.9-macosx10.9.pkg'), - PythonConfiguration(version='3.8', identifier='cp38-macosx_x86_64', url='https://www.python.org/ftp/python/3.8.7/python-3.8.7-macosx10.9.pkg'), - PythonConfiguration(version='3.9', identifier='cp39-macosx_x86_64', url='https://www.python.org/ftp/python/3.9.1/python-3.9.1-macosx10.9.pkg'), - # PyPy - PythonConfiguration(version='2.7', identifier='pp27-macosx_x86_64', url='https://downloads.python.org/pypy/pypy2.7-v7.3.3-osx64.tar.bz2'), - PythonConfiguration(version='3.6', identifier='pp36-macosx_x86_64', url='https://downloads.python.org/pypy/pypy3.6-v7.3.3-osx64.tar.bz2'), - PythonConfiguration(version='3.7', identifier='pp37-macosx_x86_64', url='https://downloads.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2'), - ] +def get_python_configurations( + build_selector: BuildSelector) -> List[PythonConfiguration]: + + full_python_configs = read_python_configs('macos') + + python_configurations = [PythonConfiguration(**item) for item in full_python_configs] # skip builds as required return [c for c in python_configurations if build_selector(c.identifier)] @@ -115,7 +109,7 @@ def install_pypy(version: str, url: str) -> Path: call(['tar', '-C', '/tmp', '-xf', downloaded_tar_bz2]) # Patch PyPy to make sure headers get installed into a venv patch_version = '_27' if version == '2.7' else '' - patch_path = Path(__file__).absolute().parent / 'resources' / f'pypy_venv{patch_version}.patch' + patch_path = resources_dir / f'pypy_venv{patch_version}.patch' call(['patch', '--force', '-p1', '-d', installation_path, '-i', patch_path]) installation_bin_path = installation_path / 'bin' diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml new file mode 100644 index 00000000..e43bc1de --- /dev/null +++ b/cibuildwheel/resources/build-platforms.toml @@ -0,0 +1,67 @@ +[linux] +python_configurations = [ + { identifier = "cp27-manylinux_x86_64", version = "2.7", path_str = "/opt/python/cp27-cp27m" }, + { identifier = "cp27-manylinux_x86_64", version = "2.7", path_str = "/opt/python/cp27-cp27mu" }, + { identifier = "cp35-manylinux_x86_64", version = "3.5", path_str = "/opt/python/cp35-cp35m" }, + { identifier = "cp36-manylinux_x86_64", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_x86_64", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { identifier = "cp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, + { identifier = "cp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, + { identifier = "cp27-manylinux_i686", version = "2.7", path_str = "/opt/python/cp27-cp27m" }, + { identifier = "cp27-manylinux_i686", version = "2.7", path_str = "/opt/python/cp27-cp27mu" }, + { identifier = "cp35-manylinux_i686", version = "3.5", path_str = "/opt/python/cp35-cp35m" }, + { identifier = "cp36-manylinux_i686", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_i686", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { identifier = "cp38-manylinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" }, + { identifier = "cp39-manylinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" }, + { identifier = "pp27-manylinux_x86_64", version = "2.7", path_str = "/opt/python/pp27-pypy_73" }, + { identifier = "pp36-manylinux_x86_64", version = "3.6", path_str = "/opt/python/pp36-pypy36_pp73" }, + { identifier = "pp37-manylinux_x86_64", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" }, + { identifier = "cp35-manylinux_aarch64", version = "3.5", path_str = "/opt/python/cp35-cp35m" }, + { identifier = "cp36-manylinux_aarch64", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { identifier = "cp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, + { identifier = "cp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, + { identifier = "cp35-manylinux_ppc64le", version = "3.5", path_str = "/opt/python/cp35-cp35m" }, + { identifier = "cp36-manylinux_ppc64le", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_ppc64le", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { identifier = "cp38-manylinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" }, + { identifier = "cp39-manylinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" }, + { identifier = "cp35-manylinux_s390x", version = "3.5", path_str = "/opt/python/cp35-cp35m" }, + { identifier = "cp36-manylinux_s390x", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_s390x", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { identifier = "cp38-manylinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" }, + { identifier = "cp39-manylinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" }, +] + +[macos] +python_configurations = [ + { identifier = "cp27-macosx_x86_64", version = "2.7", url = "https://www.python.org/ftp/python/2.7.18/python-2.7.18-macosx10.9.pkg" }, + { identifier = "cp35-macosx_x86_64", version = "3.5", url = "https://www.python.org/ftp/python/3.5.4/python-3.5.4-macosx10.6.pkg" }, + { identifier = "cp36-macosx_x86_64", version = "3.6", url = "https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg" }, + { identifier = "cp37-macosx_x86_64", version = "3.7", url = "https://www.python.org/ftp/python/3.7.9/python-3.7.9-macosx10.9.pkg" }, + { identifier = "cp38-macosx_x86_64", version = "3.8", url = "https://www.python.org/ftp/python/3.8.7/python-3.8.7-macosx10.9.pkg" }, + { identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.1/python-3.9.1-macosx10.9.pkg" }, + { identifier = "pp27-macosx_x86_64", version = "2.7", url = "https://downloads.python.org/pypy/pypy2.7-v7.3.3-osx64.tar.bz2" }, + { identifier = "pp36-macosx_x86_64", version = "3.6", url = "https://downloads.python.org/pypy/pypy3.6-v7.3.3-osx64.tar.bz2" }, + { identifier = "pp37-macosx_x86_64", version = "3.7", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2" }, +] + +[windows] +python_configurations = [ + { identifier = "cp27-win32", version = "2.7.18", arch = "32" }, + { identifier = "cp27-win_amd64", version = "2.7.18", arch = "64" }, + { identifier = "cp35-win32", version = "3.5.4", arch = "32" }, + { identifier = "cp35-win_amd64", version = "3.5.4", arch = "64" }, + { identifier = "cp36-win32", version = "3.6.8", arch = "32" }, + { identifier = "cp36-win_amd64", version = "3.6.8", arch = "64" }, + { identifier = "cp37-win32", version = "3.7.9", arch = "32" }, + { identifier = "cp37-win_amd64", version = "3.7.9", arch = "64" }, + { identifier = "cp38-win32", version = "3.8.7", arch = "32" }, + { identifier = "cp38-win_amd64", version = "3.8.7", arch = "64" }, + { identifier = "cp39-win32", version = "3.9.1", arch = "32" }, + { identifier = "cp39-win_amd64", version = "3.9.1", arch = "64" }, + { identifier = "pp27-win32", version = "2.7", arch = "32", url = "https://downloads.python.org/pypy/pypy2.7-v7.3.3-win32.zip" }, + { identifier = "pp36-win32", version = "3.6", arch = "32", url = "https://downloads.python.org/pypy/pypy3.6-v7.3.3-win32.zip" }, + { identifier = "pp37-win32", version = "3.7", arch = "32", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.3-win32.zip" }, +] diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index ffe8afb6..03a1c7c1 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -1,6 +1,13 @@ import os import subprocess -from typing import TYPE_CHECKING, Union +import sys +from typing import TYPE_CHECKING, NoReturn, Set, Union + +if sys.version_info < (3, 8): + from typing_extensions import Final, Literal +else: + from typing import Final, Literal + if TYPE_CHECKING: PopenBytes = subprocess.Popen[bytes] @@ -8,3 +15,11 @@ if TYPE_CHECKING: else: PopenBytes = subprocess.Popen PathOrStr = Union[str, "os.PathLike[str]"] + + +PlatformName = Literal["linux", "macos", "windows"] +PLATFORMS: Final[Set[PlatformName]] = {"linux", "macos", "windows"} + + +def assert_never(value: NoReturn) -> NoReturn: + assert False, f'Unhandled value: {value} ({type(value).__name__})' # noqa: B011 diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 6435252f..499b987d 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -13,14 +13,20 @@ from time import sleep from typing import Dict, List, NamedTuple, Optional, Set import certifi +import toml from .environment import ParsedEnvironment -from .typing import PathOrStr +from .typing import PathOrStr, PlatformName -if sys.version_info < (3, 8): - from typing_extensions import Literal +if sys.version_info < (3, 9): + from importlib_resources import files else: - from typing import Literal + from importlib.resources import files + + +resources_dir = files('cibuildwheel') / 'resources' +get_pip_script = resources_dir / 'get-pip.py' +install_certifi_script = resources_dir / "install_certifi.py" def prepare_command(command: str, **kwargs: PathOrStr) -> str: @@ -42,6 +48,13 @@ def get_build_verbosity_extra_flags(level: int) -> List[str]: return [] +def read_python_configs(config: PlatformName) -> List[Dict[str, str]]: + input_file = resources_dir / 'build-platforms.toml' + loaded_file = toml.load(input_file) + results: List[Dict[str, str]] = list(loaded_file[config]['python_configurations']) + return results + + class BuildSelector: def __init__(self, build_config: str, skip_config: str): self.build_patterns = build_config.split() @@ -151,7 +164,7 @@ class Architecture(Enum): return self.value < other.value @staticmethod - def parse_config(config: str, platform: str) -> 'Set[Architecture]': + def parse_config(config: str, platform: PlatformName) -> 'Set[Architecture]': result = set() for arch_str in re.split(r'[\s,]+', config): if arch_str == 'auto': @@ -161,7 +174,7 @@ class Architecture(Enum): return result @staticmethod - def auto_archs(platform: str) -> 'Set[Architecture]': + def auto_archs(platform: PlatformName) -> 'Set[Architecture]': native_architecture = Architecture(platform_module.machine()) result = {native_architecture} if platform == 'linux' and native_architecture == Architecture.x86_64: @@ -190,11 +203,6 @@ class BuildOptions(NamedTuple): build_verbosity: int -resources_dir = Path(__file__).resolve().parent / 'resources' -get_pip_script = resources_dir / 'get-pip.py' -install_certifi_script = resources_dir / "install_certifi.py" - - class NonPlatformWheelError(Exception): def __init__(self) -> None: message = textwrap.dedent(''' @@ -255,7 +263,7 @@ ALLOWED_ARCHITECTURES = { def allowed_architectures_check( - name: Literal['linux', 'macos', 'windows'], + name: PlatformName, options: BuildOptions, ) -> None: diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 355fe055..ef24fae9 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -22,6 +22,7 @@ from .util import ( get_build_verbosity_extra_flags, get_pip_script, prepare_command, + read_python_configs, ) IS_RUNNING_ON_AZURE = Path('C:\\hostedtoolcache').exists() @@ -52,35 +53,23 @@ class PythonConfiguration(NamedTuple): version: str arch: str identifier: str - url: Optional[str] + url: Optional[str] = None -def get_python_configurations(build_selector: BuildSelector, architectures: Set[Architecture]) -> List[PythonConfiguration]: +def get_python_configurations( + build_selector: BuildSelector, + architectures: Set[Architecture], +) -> List[PythonConfiguration]: + + full_python_configs = read_python_configs('windows') + + python_configurations = [PythonConfiguration(**item) for item in full_python_configs] + map_arch = { '32': Architecture.x86, '64': Architecture.AMD64, } - python_configurations = [ - # CPython - PythonConfiguration(version='2.7.18', arch='32', identifier='cp27-win32', url=None), - PythonConfiguration(version='2.7.18', arch='64', identifier='cp27-win_amd64', url=None), - PythonConfiguration(version='3.5.4', arch='32', identifier='cp35-win32', url=None), - PythonConfiguration(version='3.5.4', arch='64', identifier='cp35-win_amd64', url=None), - PythonConfiguration(version='3.6.8', arch='32', identifier='cp36-win32', url=None), - PythonConfiguration(version='3.6.8', arch='64', identifier='cp36-win_amd64', url=None), - PythonConfiguration(version='3.7.9', arch='32', identifier='cp37-win32', url=None), - PythonConfiguration(version='3.7.9', arch='64', identifier='cp37-win_amd64', url=None), - PythonConfiguration(version='3.8.7', arch='32', identifier='cp38-win32', url=None), - PythonConfiguration(version='3.8.7', arch='64', identifier='cp38-win_amd64', url=None), - PythonConfiguration(version='3.9.1', arch='32', identifier='cp39-win32', url=None), - PythonConfiguration(version='3.9.1', arch='64', identifier='cp39-win_amd64', url=None), - # PyPy - PythonConfiguration(version='2.7', arch='32', identifier='pp27-win32', url='https://downloads.python.org/pypy/pypy2.7-v7.3.3-win32.zip'), - PythonConfiguration(version='3.6', arch='32', identifier='pp36-win32', url='https://downloads.python.org/pypy/pypy3.6-v7.3.3-win32.zip'), - PythonConfiguration(version='3.7', arch='32', identifier='pp37-win32', url='https://downloads.python.org/pypy/pypy3.7-v7.3.3-win32.zip'), - ] - if IS_RUNNING_ON_TRAVIS: # cannot install VCForPython27.msi which is needed for compiling C software # try with (and similar): msiexec /i VCForPython27.msi ALLUSERS=1 ACCEPT=YES /passive @@ -210,14 +199,14 @@ def pep_518_cp35_workaround(package_dir: Path, env: Dict[str, str]) -> None: log.step('Performing PEP518 workaround...') with tempfile.TemporaryDirectory() as d: reqfile = Path(d) / "requirements.txt" - with reqfile.open("w") as f: + with reqfile.open('w') as f: for r in requirements: print(r, file=f) call(['pip', 'install', '-r', reqfile], env=env) def build(options: BuildOptions) -> None: - allowed_architectures_check("windows", options) + allowed_architectures_check('windows', options) temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel')) built_wheel_dir = temp_dir / 'built_wheel' diff --git a/setup.cfg b/setup.cfg index 3ba3b866..69893507 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ install_requires = toml certifi typing_extensions; python_version < '3.8' + importlib_resources>=1.4; python_version < '3.9' [options.package_data] cibuildwheel = resources/* @@ -112,6 +113,10 @@ ignore_missing_imports = True [mypy-bashlex.*] ignore_missing_imports = True +# Has type stubs, but no pyproject.toml or .pyi files in the wheel. +[mypy-toml.*] +ignore_missing_imports = True + [tool:isort] profile=black multi_line_output=3 diff --git a/unit_test/build_ids_test.py b/unit_test/build_ids_test.py new file mode 100644 index 00000000..99c5b57e --- /dev/null +++ b/unit_test/build_ids_test.py @@ -0,0 +1,28 @@ +import toml +from toml.encoder import TomlEncoder + +from cibuildwheel.util import resources_dir + + +class InlineArrayDictEncoder(TomlEncoder): + def dump_sections(self, o: dict, sup: str): + if all(isinstance(a, list) for a in o.values()): + val = "" + for k, v in o.items(): + inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v) + val += f"{k} = [\n {inner},\n]\n" + return val, self._dict() + else: + return super().dump_sections(o, sup) + + +def test_compare_configs(): + with open(resources_dir / "build-platforms.toml") as f: + txt = f.read() + + dict_txt = toml.loads(txt) + + new_txt = toml.dumps(dict_txt, encoder=InlineArrayDictEncoder()) + print(new_txt) + + assert new_txt == txt From 8b22048deaae7a942063ee652bd293afa4263525 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 9 Jan 2021 15:41:41 -0500 Subject: [PATCH 02/27] docs: drop double install and add a few projects (#520) * docs: project update * docs: pyproject.toml mention * Apply suggestions from code review Co-authored-by: Joe Rickerby * docs: one more change from @joerick, dropped double install example Co-authored-by: Joe Rickerby --- README.md | 98 +++++++++++++++++++++++++----------------- bin/projects.py | 11 +++-- docs/data/projects.yml | 31 +++++++++++++ docs/options.md | 39 ++++++++++++++--- 4 files changed, 129 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 276b15a2..887ac03e 100644 --- a/README.md +++ b/README.md @@ -135,11 +135,13 @@ Here are some repos that use cibuildwheel. | [websockets][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Library for building WebSocket servers and clients. Mostly written in Python, with a small C 'speedups' extension module. | | [pyzmq][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython and CFFI. | | [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. | -| [h5py][] | ![azurepipelines icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | HDF5 for Python -- The h5py package is a Pythonic interface to the HDF5 binary data format. | +| [numexpr][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast numerical array expression evaluator for Python, NumPy, PyTables, pandas, bcolz and more | | [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python | +| [h5py][] | ![azurepipelines icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | HDF5 for Python -- The h5py package is a Pythonic interface to the HDF5 binary data format. | | [River][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | 🌊 Online machine learning in Python | | [PyAV][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Pythonic bindings for FFmpeg's libraries. | | [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI | +| [PyTables][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data | | [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python | | [google neuroglancer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data | | [AutoPy][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. | @@ -147,11 +149,15 @@ Here are some repos that use cibuildwheel. | [Parselmouth][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. | | [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] ![appveyor icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson | | [Rtree][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Rtree: spatial index for Python GIS ¢ | +| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing | | [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python | -| [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system | | [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a CMake-based build system | +| [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system | +| [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub | | [iminuit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Jupyter-friendly Python interface for C++ MINUIT2 | | [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq | +| [matrixprofile][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python 3 library making time series data mining tasks, utilizing matrix profile algorithms, accessible to everyone. | +| [Tokenizer][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] | Fast and customizable text tokenization library with BPE and SentencePiece support | | [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. | | [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. | | [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast Base64 encoding/decoding in Python | @@ -172,11 +178,13 @@ Here are some repos that use cibuildwheel. [websockets]: https://github.com/aaugustin/websockets [pyzmq]: https://github.com/zeromq/pyzmq [aiortc]: https://github.com/aiortc/aiortc -[h5py]: https://github.com/h5py/h5py +[numexpr]: https://github.com/pydata/numexpr [coverage.py]: https://github.com/nedbat/coveragepy +[h5py]: https://github.com/h5py/h5py [River]: https://github.com/online-ml/river [PyAV]: https://github.com/PyAV-Org/PyAV [Dependency Injector]: https://github.com/ets-labs/python-dependency-injector +[PyTables]: https://github.com/PyTables/PyTables [aioquic]: https://github.com/aiortc/aioquic [google neuroglancer]: https://github.com/google/neuroglancer [AutoPy]: https://github.com/autopilot-rs/autopy @@ -184,11 +192,15 @@ Here are some repos that use cibuildwheel. [Parselmouth]: https://github.com/YannickJadoul/Parselmouth [python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson [Rtree]: https://github.com/Toblerity/rtree +[cyvcf2]: https://github.com/brentp/cyvcf2 [KDEpy]: https://github.com/tommyod/KDEpy -[pybind11 python_example]: https://github.com/pybind/python_example [pybind11 cmake_example]: https://github.com/pybind/cmake_example +[pybind11 python_example]: https://github.com/pybind/python_example +[dd-trace-py]: https://github.com/DataDog/dd-trace-py [iminuit]: https://github.com/scikit-hep/iminuit [jq.py]: https://github.com/mwilliamson/jq.py +[matrixprofile]: https://github.com/matrix-profile-foundation/matrixprofile +[Tokenizer]: https://github.com/OpenNMT/Tokenizer [bx-python]: https://github.com/bxlab/bx-python [boost-histogram]: https://github.com/scikit-hep/boost-histogram [pybase64]: https://github.com/mayeut/pybase64 @@ -211,42 +223,48 @@ Here are some repos that use cibuildwheel. [apple icon]: docs/data/readme_icons/apple.svg [linux icon]: docs/data/readme_icons/linux.svg - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/projects.py b/bin/projects.py index 22898cc3..c6cbbacf 100755 --- a/bin/projects.py +++ b/bin/projects.py @@ -20,7 +20,7 @@ from typing import Any, Dict, List, Optional, TextIO import click import yaml -from github import Github +from github import Github, GithubException ICONS = ( "appveyor", @@ -53,7 +53,12 @@ class Project: self.online = github is not None if github is not None: - repo = github.get_repo(self.stars_repo) + try: + repo = github.get_repo(self.stars_repo) + except GithubException: + print(f"Broken: {self.stars_repo}") + raise + self.num_stars = repo.stargazers_count self.pushed_at = repo.pushed_at if not self.notes: @@ -167,7 +172,7 @@ def str_projects( return io.getvalue() -@click.command() +@click.command(help="Try ./bin/projects.py docs/data/projects.yml --readme README.md") @click.argument("input", type=click.File("r")) @click.option("--online/--no-online", default=True, help="Get info from GitHub") @click.option("--auth", help="GitHub authentication token") diff --git a/docs/data/projects.yml b/docs/data/projects.yml index 19acaf94..8317e674 100644 --- a/docs/data/projects.yml +++ b/docs/data/projects.yml @@ -229,3 +229,34 @@ ci: [travisci] os: [windows, apple, linux] notes: Dependency injection framework for Python, uses Windows TravisCI + +- name: numexpr + gh: pydata/numexpr + ci: [github, travisci] + os: [windows, apple, linux] + +- name: PyTables + gh: PyTables/PyTables + ci: [github] + os: [windows, apple, linux] + +- name: dd-trace-py + gh: DataDog/dd-trace-py + ci: [github] + os: [windows, apple, linux] + notes: Uses custom alternate arch emulation on GitHub + +- name: Tokenizer + gh: OpenNMT/Tokenizer + ci: [github, travisci] + os: [apple, linux] + +- name: matrixprofile + gh: matrix-profile-foundation/matrixprofile + ci: [travisci] + os: [windows, apple, linux] + +- name: cyvcf2 + gh: brentp/cyvcf2 + ci: [github] + os: [apple, linux] diff --git a/docs/options.md b/docs/options.md index e12ad186..e7a9f3ba 100644 --- a/docs/options.md +++ b/docs/options.md @@ -243,7 +243,7 @@ CIBW_BEFORE_ALL_LINUX: yum install -y libffi-dev A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the `pip wheel` command. This is useful if you need to set up some dependency so it's available during the build. -If dependencies are required to build your wheel (for example if you include a header from a Python module), set this to `pip install .`, and the dependencies will be installed automatically by pip. However, this means your package will be built twice - if your package takes a long time to build, you might wish to manually list the dependencies here instead. +If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a pyproject.toml file. This is reproducible, and users who do not get your wheels (such as Alpine or ClearLinux users) will still benefit. The active Python binary can be accessed using `python`, and pip with `pip`; `cibuildwheel` makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by `cibuildwheel`. @@ -254,22 +254,47 @@ Platform-specific variants also available:
#### Examples ```yaml -# install your project and dependencies before building -CIBW_BEFORE_BUILD: pip install . - -# install something required for the build +# install something required for the build (you might want to use pyproject.toml instead) CIBW_BEFORE_BUILD: pip install pybind11 # chain commands using && -CIBW_BEFORE_BUILD: yum install -y libffi-dev && pip install . +CIBW_BEFORE_BUILD_LINUX: yum install -y libffi-dev && make clean # run a script that's inside your project CIBW_BEFORE_BUILD: bash scripts/prepare_for_build.sh # if cibuildwheel is called with a package_dir argument, it's available as {package} -CIBW_BEFORE_BUILD: "{package}/bin/prepare_for_build.sh" +CIBW_BEFORE_BUILD: "{package}/script/prepare_for_build.sh" ``` +!!! note + If you need dependencies installed for the build, we recommend using pyproject.toml. This is an example pyproject.toml file: + + ```toml + [build-system] + requires = [ + "setuptools>=42", + "wheel", + "Cython", + "numpy==1.11.3; python_version<='3.6'", + "numpy==1.14.5; python_version=='3.7'", + "numpy==1.17.3; python_version=='3.8'", + "numpy==1.19.4; python_version>='3.9'", + ] + + build-backend = "setuptools.build_meta" + ``` + + This [PEP 517][]/[PEP 518][] style build allows you to completely control the + build environment in cibuildwheel, [PyPA-build][], and pip, doesn't force + downstream users to install anything they don't need, and lets you do more + complex pinning (Cython, for example, requires a wheel to be built with an + equal or earlier version of NumPy; pinning in this way is the only way to + ensure your module works on all available NumPy versions). + + [PyPA-build]: https://pypa-build.readthedocs.io/en/latest/ + [PEP 517]: https://www.python.org/dev/peps/pep-0517/ + [PEP 518]: https://www.python.org/dev/peps/pep-0517/ ### `CIBW_REPAIR_WHEEL_COMMAND` {: #repair-wheel-command} > Execute a shell command to repair each (non-pure Python) built wheel From f6eea06dd22c0b8544b36af683078359cc221398 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 09:37:11 -0500 Subject: [PATCH 03/27] chore: update dependencies (#530) Co-authored-by: mayeut --- cibuildwheel/resources/constraints-python27.txt | 2 +- cibuildwheel/resources/constraints-python35.txt | 2 +- cibuildwheel/resources/constraints-python36.txt | 8 ++++---- cibuildwheel/resources/constraints-python37.txt | 6 +++--- cibuildwheel/resources/constraints-python38.txt | 4 ++-- cibuildwheel/resources/constraints-python39.txt | 4 ++-- cibuildwheel/resources/constraints.txt | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cibuildwheel/resources/constraints-python27.txt b/cibuildwheel/resources/constraints-python27.txt index d5d41c2d..b9456880 100644 --- a/cibuildwheel/resources/constraints-python27.txt +++ b/cibuildwheel/resources/constraints-python27.txt @@ -37,7 +37,7 @@ six==1.15.0 # virtualenv typing==3.7.4.3 # via importlib-resources -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via diff --git a/cibuildwheel/resources/constraints-python35.txt b/cibuildwheel/resources/constraints-python35.txt index d51a1299..4ff43043 100644 --- a/cibuildwheel/resources/constraints-python35.txt +++ b/cibuildwheel/resources/constraints-python35.txt @@ -18,7 +18,7 @@ importlib-resources==3.2.1 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via diff --git a/cibuildwheel/resources/constraints-python36.txt b/cibuildwheel/resources/constraints-python36.txt index 2d912194..9ed440b7 100644 --- a/cibuildwheel/resources/constraints-python36.txt +++ b/cibuildwheel/resources/constraints-python36.txt @@ -12,15 +12,15 @@ distlib==0.3.1 # via virtualenv filelock==3.0.12 # via virtualenv -importlib-metadata==3.3.0 +importlib-metadata==3.4.0 # via virtualenv -importlib-resources==4.1.1 +importlib-resources==5.0.0 # via virtualenv six==1.15.0 # via virtualenv typing-extensions==3.7.4.3 # via importlib-metadata -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -34,5 +34,5 @@ zipp==3.4.0 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.1 +setuptools==51.1.2 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python37.txt b/cibuildwheel/resources/constraints-python37.txt index 907b755b..258ab51d 100644 --- a/cibuildwheel/resources/constraints-python37.txt +++ b/cibuildwheel/resources/constraints-python37.txt @@ -12,13 +12,13 @@ distlib==0.3.1 # via virtualenv filelock==3.0.12 # via virtualenv -importlib-metadata==3.3.0 +importlib-metadata==3.4.0 # via virtualenv six==1.15.0 # via virtualenv typing-extensions==3.7.4.3 # via importlib-metadata -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -30,5 +30,5 @@ zipp==3.4.0 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.1 +setuptools==51.1.2 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 261ebccd..6cb74781 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.1 +setuptools==51.1.2 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 261ebccd..6cb74781 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.1 +setuptools==51.1.2 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index 261ebccd..6cb74781 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.2.2 +virtualenv==20.3.0 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.1 +setuptools==51.1.2 # via -r cibuildwheel/resources/constraints.in From b627134e1654f010e47fba6781519597ae68004c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 12 Jan 2021 18:34:47 -0500 Subject: [PATCH 04/27] docs: normalize CI list (#532) * docs: normalize CI list Closes #531 * Apply suggestions from code review Co-authored-by: Joe Rickerby Co-authored-by: Joe Rickerby --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 887ac03e..2159843b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ cibuildwheel Python wheels are great. Building them across **Mac, Linux, Windows**, on **multiple versions of Python**, is not. -`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, and CircleCI - and it builds and tests your wheels across all of your platforms. +`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, and GitLab CI - and it builds and tests your wheels across all of your platforms. What does it do? @@ -31,21 +31,22 @@ What does it do? Β² Only supported on Travis
Β³ Beta support until Travis CI fixes a bug
-- Builds manylinux, macOS and Windows wheels for CPython and PyPy using Azure Pipelines, Travis CI, AppVeyor, and CircleCI +- Builds manylinux, macOS, and Windows wheels for CPython and PyPy +- Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, and GitLab CI - Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate) - Runs the library test suite against the wheel-installed version of your library Usage ----- -`cibuildwheel` currently works on **Travis CI**, **Azure Pipelines**, **AppVeyor**, **GitHub Actions**, **CircleCI**, and **Gitlab CI**. Check the table below for supported platforms on each service: +`cibuildwheel` runs inside a CI service. Supported platforms depend on which service you're using: | | Linux | macOS | Windows | Linux ARM | |-----------------|-------|-------|---------|--------------| +| GitHub Actions | βœ… | βœ… | βœ… | ✴️¹ | | Azure Pipelines | βœ… | βœ… | βœ… | ✴️¹ | | Travis CI | βœ… | βœ… | βœ… | βœ… | | AppVeyor | βœ… | βœ… | βœ… | | -| GitHub Actions | βœ… | βœ… | βœ… | ✴️¹ | | CircleCI | βœ… | βœ… | | | | Gitlab CI | βœ… | | | | @@ -92,7 +93,7 @@ jobs: path: ./wheelhouse/*.whl ``` -For more information, including building on Python 2, PyPI deployment, and the use of other CI services like Travis CI, Appveyor, Azure Pipelines, or CircleCI, check out the [documentation](https://cibuildwheel.readthedocs.org) and the [examples](https://github.com/joerick/cibuildwheel/tree/master/examples). +For more information, including building on Python 2, PyPI deployment, and the use of other CI services, check out the [documentation](https://cibuildwheel.readthedocs.org) and the [examples](https://github.com/joerick/cibuildwheel/tree/master/examples). Options ------- From ca7871ce0c2fa8c506f21593ef964ab1c42b7f3f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 15 Jan 2021 09:38:47 -0500 Subject: [PATCH 05/27] feat: native / all archs (#535) * feat: native / all archs * docs: mention new options --- cibuildwheel/__main__.py | 2 +- cibuildwheel/util.py | 31 ++++++++++++++-------- docs/options.md | 8 +++++- unit_test/main_tests/main_platform_test.py | 30 +++++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 5ca9c104..7f14ee81 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -76,7 +76,7 @@ def main() -> None: on this machine. Set this option to build an architecture via emulation, for example, using binfmt_misc and QEMU. Default: auto. - Choices: auto, {} + Choices: auto, native, all, {} '''.format(", ".join(a.name for a in Architecture))) parser.add_argument('--output-dir', default=os.environ.get('CIBW_OUTPUT_DIR', 'wheelhouse'), diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 499b987d..f0062e25 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -16,7 +16,7 @@ import certifi import toml from .environment import ParsedEnvironment -from .typing import PathOrStr, PlatformName +from .typing import PathOrStr, PlatformName, assert_never if sys.version_info < (3, 9): from importlib_resources import files @@ -169,6 +169,10 @@ class Architecture(Enum): for arch_str in re.split(r'[\s,]+', config): if arch_str == 'auto': result |= Architecture.auto_archs(platform=platform) + elif arch_str == 'native': + result.add(Architecture(platform_module.machine())) + elif arch_str == 'all': + result |= Architecture.all_archs(platform=platform) else: result.add(Architecture(arch_str)) return result @@ -184,6 +188,17 @@ class Architecture(Enum): result.add(Architecture.x86) return result + @staticmethod + def all_archs(platform: PlatformName) -> 'Set[Architecture]': + if platform == 'linux': + return {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x} + elif platform == 'macos': + return {Architecture.x86_64} + elif platform == 'windows': + return {Architecture.x86, Architecture.AMD64} + else: + assert_never(platform) + class BuildOptions(NamedTuple): package_dir: Path @@ -255,23 +270,17 @@ def detect_ci_provider() -> Optional[CIProvider]: PRETTY_NAMES = {'linux': 'Linux', 'macos': 'macOS', 'windows': 'Windows'} -ALLOWED_ARCHITECTURES = { - 'linux': {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x}, - 'macos': {Architecture.x86_64}, - 'windows': {Architecture.AMD64, Architecture.x86}, -} - def allowed_architectures_check( - name: PlatformName, + platform: PlatformName, options: BuildOptions, ) -> None: - allowed_architectures = ALLOWED_ARCHITECTURES[name] + allowed_architectures = Architecture.all_archs(platform) - msg = f'{PRETTY_NAMES[name]} only supports {sorted(allowed_architectures)} at the moment.' + msg = f'{PRETTY_NAMES[platform]} only supports {sorted(allowed_architectures)} at the moment.' - if name != 'linux': + if platform != 'linux': msg += ' If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX instead.' if not options.architectures <= allowed_architectures: diff --git a/docs/options.md b/docs/options.md index e7a9f3ba..7b2398a7 100644 --- a/docs/options.md +++ b/docs/options.md @@ -163,11 +163,17 @@ emulation, such as that provided by [docker/setup-qemu-action][setup-qemu-action or [tonistiigi/binfmt][binfmt], to build architectures other than those your machine natively supports. -Options: `auto` `x86_64` `i686` `aarch64` `ppc64le` `s390x` +Options: `auto` `native` `all` `x86_64` `i686` `aarch64` `ppc64le` `s390x` Default: `auto`, meaning the native archs supported on the build machine. For example, on an `x86_64` machine, `auto` expands to `x86_64` and `i686`. +`native` will only build on the exact architecture you currently are on; it will +not add `i686` for `x86_64`. + +`all` will expand to all known architectures; remember to use build selectors +to limit builds for each job; this list could grow in the future. + [setup-qemu-action]: https://github.com/docker/setup-qemu-action [binfmt]: https://hub.docker.com/r/tonistiigi/binfmt diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index e01526ea..9b7c06fe 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -110,3 +110,33 @@ def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): assert build_options.architectures == {Architecture.x86} elif platform == 'macos': assert build_options.architectures == {Architecture.x86_64} + + +def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): + monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') + monkeypatch.setenv('CIBW_ARCHS', 'native') + + main() + build_options = intercepted_build_args.args[0] + + if platform == 'linux': + assert build_options.architectures == {Architecture.x86_64} + elif platform == 'windows': + assert build_options.architectures == {Architecture.x86_64} + elif platform == 'macos': + assert build_options.architectures == {Architecture.x86_64} + + +def test_archs_platform_all(platform, intercepted_build_args, monkeypatch): + monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') + monkeypatch.setenv('CIBW_ARCHS', 'all') + + main() + build_options = intercepted_build_args.args[0] + + if platform == 'linux': + assert build_options.architectures == {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x} + elif platform == 'windows': + assert build_options.architectures == {Architecture.x86, Architecture.AMD64} + elif platform == 'macos': + assert build_options.architectures == {Architecture.x86_64} From d394386318c554707946a2ad99d8bc1e5e99757c Mon Sep 17 00:00:00 2001 From: Matthieu Darbois Date: Fri, 15 Jan 2021 15:40:03 +0100 Subject: [PATCH 06/27] feat: update python versions as part of update_dependencies (#496) * Update python versions as part of update_dependencies * refactor: pulling out to update_pythons * refactor: rewrite and expect install * feat: support macos too, logging output * WIP: update * refactor: drive from original file * Remove unused variable * Output a diff instead of the result file, to review changes more easily * fix: minor cleanup Co-authored-by: Henry Fredrick Schreiner Co-authored-by: Henry Schreiner Co-authored-by: Joe Rickerby --- .github/workflows/update-dependencies.yml | 6 +- .pre-commit-config.yaml | 10 +- bin/update_dependencies.py | 6 +- bin/update_pythons.py | 310 ++++++++++++++++++++++ cibuildwheel/extra.py | 24 ++ cibuildwheel/typing.py | 7 +- setup.cfg | 4 +- unit_test/build_ids_test.py | 37 ++- 8 files changed, 384 insertions(+), 20 deletions(-) create mode 100755 bin/update_pythons.py create mode 100644 cibuildwheel/extra.py diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 1b77f657..c483b270 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -17,9 +17,11 @@ jobs: python-version: 3.9 architecture: x64 - name: Install dependencies - run: python -m pip install requests pip-tools - - name: Run update + run: python -m pip install ".[dev]" + - name: "Run update: dependencies" run: python ./bin/update_dependencies.py + - name: "Run update: python configs" + run: python ./bin/update_pythons.py --force - name: Create Pull Request if: github.ref == 'refs/heads/master' uses: peter-evans/create-pull-request@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 020d0068..623e5d60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,20 @@ repos: hooks: - id: isort +- repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + files: ^bin/update_pythons.py$ + args: ["--line-length=120"] + - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.790 hooks: - id: mypy - files: ^(cibuildwheel/|test/|bin/projects.py|unit_test/) + files: ^(cibuildwheel/|test/|bin/projects.py|bin/update_pythons.py|unit_test/) pass_filenames: false + additional_dependencies: [packaging, click] - repo: https://github.com/asottile/pyupgrade rev: v2.7.4 diff --git a/bin/update_dependencies.py b/bin/update_dependencies.py index 62717c31..bd113eb5 100755 --- a/bin/update_dependencies.py +++ b/bin/update_dependencies.py @@ -28,8 +28,8 @@ if '--no-docker' in sys.argv: '--output-file', f'cibuildwheel/resources/constraints-python{python_version}.txt' ]) else: - image = 'quay.io/pypa/manylinux2010_x86_64:latest' - subprocess.check_call(['docker', 'pull', image]) + image_runner = 'quay.io/pypa/manylinux2010_x86_64:latest' + subprocess.check_call(['docker', 'pull', image_runner]) for python_version in PYTHON_VERSIONS: abi_flags = '' if int(python_version) >= 38 else 'm' python_path = f'/opt/python/cp{python_version}-cp{python_version}{abi_flags}/bin/' @@ -37,7 +37,7 @@ else: 'docker', 'run', '--rm', '-e', 'CUSTOM_COMPILE_COMMAND', '-v', f'{os.getcwd()}:/volume', - '--workdir', '/volume', image, + '--workdir', '/volume', image_runner, 'bash', '-c', f'{python_path}pip install pip-tools &&' f'{python_path}pip-compile --allow-unsafe --upgrade ' diff --git a/bin/update_pythons.py b/bin/update_pythons.py new file mode 100755 index 00000000..04f075de --- /dev/null +++ b/bin/update_pythons.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 + +import copy +import difflib +import logging +from pathlib import Path +from typing import Dict, Optional, Union + +import click +import requests +import rich +import toml +from packaging.specifiers import Specifier +from packaging.version import Version +from rich.logging import RichHandler +from rich.syntax import Syntax + +from cibuildwheel.extra import InlineArrayDictEncoder +from cibuildwheel.typing import Final, Literal, TypedDict + +log = logging.getLogger("cibw") + +# Looking up the dir instead of using utils.resources_dir +# since we want to write to it. +DIR: Final[Path] = Path(__file__).parent.parent.resolve() +RESOURCES_DIR: Final[Path] = DIR / "cibuildwheel/resources" + + +ArchStr = Literal["32", "64"] + + +class ConfigWinCP(TypedDict): + identifier: str + version: str + arch: str + + +class ConfigWinPP(TypedDict): + identifier: str + version: str + arch: str + url: str + + +class ConfigMacOS(TypedDict): + identifier: str + version: str + url: str + + +AnyConfig = Union[ConfigWinCP, ConfigWinPP, ConfigMacOS] + + +# The following set of "Versions" classes allow the initial call to the APIs to +# be cached and reused in the `update_version_*` methods. + + +class WindowsVersions: + def __init__(self, arch_str: ArchStr) -> None: + + response = requests.get("https://api.nuget.org/v3/index.json") + response.raise_for_status() + api_info = response.json() + + for resource in api_info["resources"]: + if resource["@type"] == "PackageBaseAddress/3.0.0": + endpoint = resource["@id"] + + ARCH_DICT = {"32": "win32", "64": "win_amd64"} + PACKAGE_DICT = {"32": "pythonx86", "64": "python"} + + self.arch_str = arch_str + self.arch = ARCH_DICT[arch_str] + package = PACKAGE_DICT[arch_str] + + response = requests.get(f"{endpoint}{package}/index.json") + response.raise_for_status() + cp_info = response.json() + + versions = (Version(v) for v in cp_info["versions"]) + self.versions = sorted(v for v in versions if not v.is_devrelease) + + def update_version_windows(self, spec: Specifier) -> Optional[ConfigWinCP]: + versions = sorted(v for v in self.versions if spec.contains(v)) + if not all(v.is_prerelease for v in versions): + versions = [v for v in versions if not v.is_prerelease] + log.debug(f"Windows {self.arch} {spec} has {', '.join(str(v) for v in versions)}") + + if not versions: + return None + + version = versions[-1] + identifier = f"cp{version.major}{version.minor}-{self.arch}" + result = ConfigWinCP( + identifier=identifier, + version=str(version), + arch=self.arch_str, + ) + return result + + +class PyPyVersions: + def __init__(self, arch_str: ArchStr): + + response = requests.get("https://downloads.python.org/pypy/versions.json") + response.raise_for_status() + + releases = [r for r in response.json() if r["pypy_version"] != "nightly"] + for release in releases: + release["pypy_version"] = Version(release["pypy_version"]) + release["python_version"] = Version(release["python_version"]) + + self.releases = [ + r for r in releases if not r["pypy_version"].is_prerelease and not r["pypy_version"].is_devrelease + ] + self.arch = arch_str + + def update_version_windows(self, spec: Specifier) -> ConfigWinCP: + if self.arch != "32": + raise RuntimeError("64 bit releases not supported yet on Windows") + + releases = [r for r in self.releases if spec.contains(r["python_version"])] + releases = sorted(releases, key=lambda r: r["pypy_version"]) + + if not releases: + raise RuntimeError(f"PyPy Win {self.arch} not found for {spec}! {self.releases}") + + release = releases[-1] + version = release["python_version"] + identifier = f"pp{version.major}{version.minor}-win32" + + (url,) = [rf["download_url"] for rf in release["files"] if "" in rf["platform"] == "win32"] + + return ConfigWinPP( + identifier=identifier, + version=f"{version.major}.{version.minor}", + arch="32", + url=url, + ) + + def update_version_macos(self, spec: Specifier) -> ConfigMacOS: + if self.arch != "64": + raise RuntimeError("Other archs not supported yet on macOS") + + releases = [r for r in self.releases if spec.contains(r["python_version"])] + releases = sorted(releases, key=lambda r: r["pypy_version"]) + + if not releases: + raise RuntimeError(f"PyPy macOS {self.arch} not found for {spec}!") + + release = releases[-1] + version = release["python_version"] + identifier = f"pp{version.major}{version.minor}-macosx_x86_64" + + (url,) = [ + rf["download_url"] for rf in release["files"] if "" in rf["platform"] == "darwin" and rf["arch"] == "x64" + ] + + return ConfigMacOS( + identifier=identifier, + version=f"{version.major}.{version.minor}", + url=url, + ) + + +class CPythonVersions: + def __init__(self, plat_arch: str, file_ident: str) -> None: + + response = requests.get("https://www.python.org/api/v2/downloads/release/?is_published=true") + response.raise_for_status() + + releases_info = response.json() + + self.versions_dict: Dict[Version, int] = {} + for release in releases_info: + # Removing the prefix, Python 3.9 would use: release["name"].removeprefix("Python ") + version = Version(release["name"][7:]) + + if not version.is_prerelease and not version.is_devrelease: + uri = int(release["resource_uri"].rstrip("/").split("/")[-1]) + self.versions_dict[version] = uri + + self.file_ident = file_ident + self.plat_arch = plat_arch + + def update_version_macos(self, spec: Specifier) -> Optional[ConfigMacOS]: + + sorted_versions = sorted(v for v in self.versions_dict if spec.contains(v)) + + for version in reversed(sorted_versions): + # Find the first patch version that contains the requested file + uri = self.versions_dict[version] + response = requests.get(f"https://www.python.org/api/v2/downloads/release_file/?release={uri}") + response.raise_for_status() + file_info = response.json() + + urls = [rf["url"] for rf in file_info if self.file_ident in rf["url"]] + if urls: + return ConfigMacOS( + identifier=f"cp{version.major}{version.minor}-{self.plat_arch}", + version=f"{version.major}.{version.minor}", + url=urls[0], + ) + + return None + + +# This is a universal interface to all the above Versions classes. Given an +# identifier, it updates a config dict. + + +class AllVersions: + def __init__(self) -> None: + self.windows_32 = WindowsVersions("32") + self.windows_64 = WindowsVersions("64") + self.windows_pypy = PyPyVersions("32") + + self.macos_6 = CPythonVersions(plat_arch="macosx_x86_64", file_ident="macosx10.6.pkg") + self.macos_9 = CPythonVersions(plat_arch="macosx_x86_64", file_ident="macosx10.9.pkg") + self.macos_u2 = CPythonVersions(plat_arch="macosx_universal2", file_ident="macos11.0.pkg") + self.macos_pypy = PyPyVersions("64") + + def update_config(self, config: Dict[str, str]) -> None: + identifier = config["identifier"] + version = Version(config["version"]) + spec = Specifier(f"=={version.major}.{version.minor}.*") + log.info(f"Reading in '{identifier}' -> {spec} @ {version}") + orig_config = copy.copy(config) + config_update: Optional[AnyConfig] + + # We need to use ** in update due to MyPy (probably a bug) + if "macosx_x86_64" in identifier: + if identifier.startswith("pp"): + config_update = self.macos_pypy.update_version_macos(spec) + else: + config_update = self.macos_9.update_version_macos(spec) or self.macos_6.update_version_macos(spec) + assert config_update is not None, f"MacOS {spec} not found!" + config.update(**config_update) + elif "win32" in identifier: + if identifier.startswith("pp"): + config.update(**self.windows_pypy.update_version_windows(spec)) + else: + config_update = self.windows_32.update_version_windows(spec) + if config_update: + config.update(**config_update) + elif "win_amd64" in identifier: + config_update = self.windows_64.update_version_windows(spec) + if config_update: + config.update(**config_update) + + if config != orig_config: + log.info(f" Updated {orig_config} to {config}") + + +@click.command() +@click.option("--force", is_flag=True) +@click.option("--level", default="INFO", type=click.Choice(["INFO", "DEBUG", "TRACE"], case_sensitive=False)) +def update_pythons(force: bool, level: str) -> None: + + logging.basicConfig( + level="INFO", + format="%(message)s", + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True, markup=True)], + ) + log.setLevel(level) + + all_versions = AllVersions() + toml_file_path = RESOURCES_DIR / "build-platforms.toml" + + original_toml = toml_file_path.read_text() + configs = toml.loads(original_toml) + + for config in configs["windows"]["python_configurations"]: + all_versions.update_config(config) + + for config in configs["macos"]["python_configurations"]: + all_versions.update_config(config) + + result_toml = toml.dumps(configs, encoder=InlineArrayDictEncoder()) # type: ignore + + rich.print() # spacer + + if original_toml == result_toml: + rich.print("[green]Check complete, Python configurations unchanged.") + return + + rich.print("Python configurations updated.") + rich.print("Changes:") + rich.print() + + toml_relpath = toml_file_path.relative_to(DIR).as_posix() + diff_lines = difflib.unified_diff( + original_toml.splitlines(keepends=True), + result_toml.splitlines(keepends=True), + fromfile=toml_relpath, + tofile=toml_relpath, + ) + rich.print(Syntax("".join(diff_lines), "diff", theme="ansi_light")) + rich.print() + + if force: + toml_file_path.write_text(result_toml) + rich.print("[green]TOML file updated.") + else: + rich.print("[yellow]File left unchanged. Use --force flag to update.") + + +if __name__ == "__main__": + update_pythons() diff --git a/cibuildwheel/extra.py b/cibuildwheel/extra.py new file mode 100644 index 00000000..3c2ef4fb --- /dev/null +++ b/cibuildwheel/extra.py @@ -0,0 +1,24 @@ +""" +These are utilities for the `/bin` scripts, not for the `cibuildwheel` program. +""" + +from typing import Any, Dict + +import toml.encoder +from packaging.version import Version + + +class InlineArrayDictEncoder(toml.encoder.TomlEncoder): # type: ignore + def __init__(self) -> None: + super().__init__() + self.dump_funcs[Version] = lambda v: f'"{v}"' + + def dump_sections(self, o: Dict[str, Any], sup: str) -> Any: + if all(isinstance(a, list) for a in o.values()): + val = "" + for k, v in o.items(): + inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v) + val += f"{k} = [\n {inner},\n]\n" + return val, self._dict() + else: + return super().dump_sections(o, sup) diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index 03a1c7c1..9a650d54 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -4,9 +4,12 @@ import sys from typing import TYPE_CHECKING, NoReturn, Set, Union if sys.version_info < (3, 8): - from typing_extensions import Final, Literal + from typing_extensions import Final, Literal, TypedDict else: - from typing import Final, Literal + from typing import Final, Literal, TypedDict + + +__all__ = ("Final", "Literal", "TypedDict", "Set", "Union", "PopenBytes", "PathOrStr", "PlatformName", "PLATFORMS", "assert_never") if TYPE_CHECKING: diff --git a/setup.cfg b/setup.cfg index 69893507..2220ab45 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,8 @@ dev = pyyaml requests typing-extensions + packaging>=20.8 + rich>=9.6 [options.packages.find] include = @@ -81,7 +83,7 @@ junit_family=xunit2 [mypy] python_version = 3.6 -files = cibuildwheel,test,unit_test +files = cibuildwheel,test,unit_test,bin warn_unused_configs = True warn_redundant_casts = True diff --git a/unit_test/build_ids_test.py b/unit_test/build_ids_test.py index 99c5b57e..1993efd0 100644 --- a/unit_test/build_ids_test.py +++ b/unit_test/build_ids_test.py @@ -1,19 +1,11 @@ +import pytest import toml -from toml.encoder import TomlEncoder from cibuildwheel.util import resources_dir +Version = pytest.importorskip("packaging.version").Version -class InlineArrayDictEncoder(TomlEncoder): - def dump_sections(self, o: dict, sup: str): - if all(isinstance(a, list) for a in o.values()): - val = "" - for k, v in o.items(): - inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v) - val += f"{k} = [\n {inner},\n]\n" - return val, self._dict() - else: - return super().dump_sections(o, sup) +from cibuildwheel.extra import InlineArrayDictEncoder # noqa: E402 def test_compare_configs(): @@ -26,3 +18,26 @@ def test_compare_configs(): print(new_txt) assert new_txt == txt + + +def test_dump_with_Version(): + example = { + "windows": { + "python_configurations": [ + {"identifier": "cp27-win32", "version": Version("2.7.18"), "arch": "32"}, + {"identifier": "cp27-win_amd64", "version": Version("2.7.18"), "arch": "64"}, + ] + } + } + + result = """\ +[windows] +python_configurations = [ + { identifier = "cp27-win32", version = "2.7.18", arch = "32" }, + { identifier = "cp27-win_amd64", version = "2.7.18", arch = "64" }, +] +""" + + output = toml.dumps(example, encoder=InlineArrayDictEncoder()) + print(output) + assert output == result From e91d8d82a2b849bd5256e930d65d0f7513a6bbf6 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 15 Jan 2021 15:25:22 -0500 Subject: [PATCH 07/27] feat: support brace expansion in BUILD/SKIP (#527) * wip: try wcmatch * feat: expand braces * docs: mention brace expansion * docs: more reasonable examples of brackets * Update docs/options.md Co-authored-by: Joe Rickerby Co-authored-by: Joe Rickerby --- cibuildwheel/util.py | 13 +++++++++---- docs/options.md | 10 +++++----- setup.cfg | 4 ++++ unit_test/build_selector_test.py | 10 ++++++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index f0062e25..bfc54d5a 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -1,4 +1,6 @@ +import fnmatch import functools +import itertools import os import platform as platform_module import re @@ -7,11 +9,11 @@ import sys import textwrap import urllib.request from enum import Enum -from fnmatch import fnmatch from pathlib import Path from time import sleep from typing import Dict, List, NamedTuple, Optional, Set +import bracex import certifi import toml @@ -61,9 +63,12 @@ class BuildSelector: self.skip_patterns = skip_config.split() def __call__(self, build_id: str) -> bool: - def match_any(patterns: List[str]) -> bool: - return any(fnmatch(build_id, pattern) for pattern in patterns) - return match_any(self.build_patterns) and not match_any(self.skip_patterns) + build_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in self.build_patterns) + skip_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in self.skip_patterns) + + build: bool = any(fnmatch.fnmatch(build_id, pat) for pat in build_patterns) + skip: bool = any(fnmatch.fnmatch(build_id, pat) for pat in skip_patterns) + return build and not skip def __repr__(self) -> str: if not self.skip_patterns: diff --git a/docs/options.md b/docs/options.md index 7b2398a7..a65a5796 100644 --- a/docs/options.md +++ b/docs/options.md @@ -75,7 +75,7 @@ Space-separated list of builds to build and skip. Each build has an identifier l When both options are specified, both conditions are applied and only builds with a tag that matches `CIBW_BUILD` and does not match `CIBW_SKIP` will be built. -When setting the options, you can use shell-style globbing syntax (as per [`fnmatch`](https://docs.python.org/3/library/fnmatch.html)). All the build identifiers supported by cibuildwheel are shown below: +When setting the options, you can use shell-style globbing syntax, as per [`fnmatch`](https://docs.python.org/3/library/fnmatch.html) with the addition of curly bracket syntax `{option1,option2}`, provided by [`bracex`](https://pypi.org/project/bracex/). All the build identifiers supported by cibuildwheel are shown below:
@@ -89,7 +89,7 @@ When setting the options, you can use shell-style globbing syntax (as per [`fnma | Python 3.9 | cp39-macosx_x86_64 | cp39-manylinux_x86_64 | cp39-manylinux_i686 | cp39-win_amd64 | cp39-win32 | cp39-manylinux_aarch64 | cp39-manylinux_ppc64le | cp39-manylinux_s390x | | PyPy 2.7 v7.3.3 | pp27-macosx_x86_64 | pp27-manylinux_x86_64 | | | pp27-win32 | | | | | PyPy 3.6 v7.3.3 | pp36-macosx_x86_64 | pp36-manylinux_x86_64 | | | pp36-win32 | | | | -| PyPy 3.7 (beta) v7.3.3 | pp37-macosx_x86_64 | pp37-manylinux_x86_64 | | | pp37-win32 | | | | +| PyPy 3.7 (beta) v7.3.3 | pp37-macosx_x86_64 | pp37-manylinux_x86_64 | | | pp37-win32 | | | | The list of supported and currently selected build identifiers can also be retrieved by passing the `--print-build-identifiers` flag to `cibuildwheel`. @@ -124,12 +124,12 @@ CIBW_SKIP: cp27-* cp35-* # Skip Python 3.6 on Linux CIBW_SKIP: cp36-manylinux* -# Only build on Python 3 and skip 32-bit builds -CIBW_BUILD: cp3?-* +# Only build on Python 3 (ready for 3.10 when it comes) and skip 32-bit builds +CIBW_BUILD: {cp,pp}3*-* CIBW_SKIP: "*-win32 *-manylinux_i686" # Only build PyPy and CPython 3 -CIBW_BUILD: pp* cp3?-* +CIBW_BUILD: pp* cp3*-* # Disable building PyPy wheels on all platforms CIBW_SKIP: pp* diff --git a/setup.cfg b/setup.cfg index 2220ab45..da6fe65b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ install_requires = bashlex!=0.13 toml certifi + bracex typing_extensions; python_version < '3.8' importlib_resources>=1.4; python_version < '3.9' @@ -119,6 +120,9 @@ ignore_missing_imports = True [mypy-toml.*] ignore_missing_imports = True +[mypy-bracex.*] +ignore_missing_imports = True + [tool:isort] profile=black multi_line_output=3 diff --git a/unit_test/build_selector_test.py b/unit_test/build_selector_test.py index 8737ee6b..6e00fc76 100644 --- a/unit_test/build_selector_test.py +++ b/unit_test/build_selector_test.py @@ -59,3 +59,13 @@ def test_build_and_skip(): assert not build_selector('cp27-win_amd64') assert build_selector('cp36-win_amd64') assert not build_selector('cp37-win_amd64') + + +def test_build_braces(): + build_selector = BuildSelector(build_config="cp{36,37}*", skip_config="") + + assert not build_selector('cp27-manylinux1_x86_64') + assert build_selector('cp36-manylinux1_x86_64') + assert build_selector('cp37-manylinux1_x86_64') + assert not build_selector('cp38-manylinux1_x86_64') + assert not build_selector('cp39-manylinux1_x86_64') From fbd5bc707fade658ac277738b200a183127485ee Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 16 Jan 2021 11:49:04 +0000 Subject: [PATCH 08/27] Update pinned version --- examples/github-with-qemu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/github-with-qemu.yml b/examples/github-with-qemu.yml index 95a980f2..e665ad23 100644 --- a/examples/github-with-qemu.yml +++ b/examples/github-with-qemu.yml @@ -20,7 +20,7 @@ jobs: - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==1.7.1 + python -m pip install cibuildwheel==1.7.4 - name: Install Visual C++ for Python 2.7 if: runner.os == 'Windows' From 45c3c2ea8ec5a06b2c7ede465f85f90ac0c41a05 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 17 Jan 2021 13:08:55 -0500 Subject: [PATCH 09/27] fix: empty image check (#543) --- cibuildwheel/docker_container.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cibuildwheel/docker_container.py b/cibuildwheel/docker_container.py index aee933d2..e18eed8c 100644 --- a/cibuildwheel/docker_container.py +++ b/cibuildwheel/docker_container.py @@ -30,6 +30,9 @@ class DockerContainer: bash_stdout: IO[bytes] def __init__(self, docker_image: str, simulate_32_bit: bool = False, cwd: Optional[PathOrStr] = None): + if not docker_image: + raise ValueError("Must have a non-empty docker image to run.") + self.docker_image = docker_image self.simulate_32_bit = simulate_32_bit self.cwd = cwd From 14419720464e4011b92e7de29115808f9907b4f8 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 17 Jan 2021 14:13:26 -0500 Subject: [PATCH 10/27] fix: improve correctness of exits (#539) * fix: improve correctness of exits * refactor: use sys.exit --- bin/bump_version.py | 16 ++++++++++------ bin/make_dependency_update_pr.py | 3 ++- bin/run_example_ci_configs.py | 2 +- bin/sample_build.py | 2 +- cibuildwheel/__main__.py | 14 +++++++------- cibuildwheel/linux.py | 8 ++++---- cibuildwheel/macos.py | 6 +++--- cibuildwheel/windows.py | 6 +++--- 8 files changed, 31 insertions(+), 26 deletions(-) diff --git a/bin/bump_version.py b/bin/bump_version.py index 1520108a..498d3ad2 100755 --- a/bin/bump_version.py +++ b/bin/bump_version.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 + import glob import os import subprocess +import sys import urllib.parse from pathlib import Path @@ -23,6 +25,8 @@ config = [ ] +# This file requires Python 3.7 +# Setting -> None will cause MyPy to notice this. @click.command() def bump_version(): current_version = cibuildwheel.__version__ @@ -49,7 +53,7 @@ def bump_version(): if repo_has_uncommitted_changes: print('error: Uncommitted changes detected.') - exit(1) + sys.exit(1) print( 'Current version:', current_version) # noqa new_version = input(' New version: ').strip() @@ -59,7 +63,7 @@ def bump_version(): except InvalidVersion: print("error: This version doesn't conform to PEP440") print(' https://www.python.org/dev/peps/pep-0440/') - exit(1) + sys.exit(1) actions = [] @@ -68,7 +72,7 @@ def bump_version(): if not paths: print(f'error: Pattern {path_pattern} didn’t match any files') - exit(1) + sys.exit(1) find_pattern = version_pattern.format(current_version) replace_pattern = version_pattern.format(new_version) @@ -83,8 +87,8 @@ def bump_version(): ) if not found_at_least_one_file_needing_update: - print(f'error: Didn’t find any occurences of β€œ{find_pattern}” in β€œ{path_pattern}”') - exit(1) + print(f'error: Didn’t find any occurrences of β€œ{find_pattern}” in β€œ{path_pattern}”') + sys.exit(1) print() print("Here's the plan:") @@ -102,7 +106,7 @@ def bump_version(): if answer != 'y': print('Aborted') - exit(1) + sys.exit(1) for path, find, replace in actions: contents = path.read_text(encoding='utf8') diff --git a/bin/make_dependency_update_pr.py b/bin/make_dependency_update_pr.py index b89d371b..902d3f6d 100755 --- a/bin/make_dependency_update_pr.py +++ b/bin/make_dependency_update_pr.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import sys import textwrap import time from pathlib import Path @@ -26,7 +27,7 @@ def main(): if git_repo_has_changes(): print('Your git repo has uncommitted changes. Commit or stash before continuing.') - exit(1) + sys.exit(1) previous_branch = shell('git rev-parse --abbrev-ref HEAD', check=True, diff --git a/bin/run_example_ci_configs.py b/bin/run_example_ci_configs.py index 6d99f59f..ad5f6a41 100755 --- a/bin/run_example_ci_configs.py +++ b/bin/run_example_ci_configs.py @@ -98,7 +98,7 @@ def run_example_ci_configs(config_files=None): if git_repo_has_changes(): print('Your git repo has uncommitted changes. Commit or stash before continuing.') - exit(1) + sys.exit(1) previous_branch = shell('git rev-parse --abbrev-ref HEAD', check=True, diff --git a/bin/sample_build.py b/bin/sample_build.py index 5879f3f1..3389f535 100755 --- a/bin/sample_build.py +++ b/bin/sample_build.py @@ -22,4 +22,4 @@ if __name__ == '__main__': options.project_python_path, project_dir ], check=True) - exit(subprocess.run(['cibuildwheel'], cwd=project_dir).returncode) + sys.exit(subprocess.run(['cibuildwheel'], cwd=project_dir).returncode) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 7f14ee81..96f6a5c8 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -110,7 +110,7 @@ def main() -> None: supported. You can run on your development machine or other CI providers using the --platform argument. Check --help output for more information. '''), file=sys.stderr) - exit(2) + sys.exit(2) if sys.platform.startswith('linux'): platform = 'linux' elif sys.platform == 'darwin': @@ -121,11 +121,11 @@ def main() -> None: print('cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run ' 'cibuildwheel using the --platform argument. Check --help output for more information.', file=sys.stderr) - exit(2) + sys.exit(2) if platform not in PLATFORMS: print(f'cibuildwheel: Unsupported platform: {platform}', file=sys.stderr) - exit(2) + sys.exit(2) package_dir = Path(args.package_dir) output_dir = Path(args.output_dir) @@ -158,7 +158,7 @@ def main() -> None: except (EnvironmentParseError, ValueError): print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr) traceback.print_exc(None, sys.stderr) - exit(2) + sys.exit(2) if dependency_versions == 'pinned': dependency_constraints: Optional[DependencyConstraints] = DependencyConstraints.with_defaults() @@ -183,7 +183,7 @@ def main() -> None: if not any((package_dir / name).exists() for name in ["setup.py", "setup.cfg", "pyproject.toml"]): print('cibuildwheel: Could not find setup.py, setup.cfg or pyproject.toml at root of package', file=sys.stderr) - exit(2) + sys.exit(2) if args.archs is not None: archs_config_str = args.archs @@ -193,7 +193,7 @@ def main() -> None: if args.print_build_identifiers: print_build_identifiers(platform, build_selector, archs) - exit(0) + sys.exit(0) manylinux_images: Optional[Dict[str, str]] = None if platform == 'linux': @@ -271,7 +271,7 @@ def detect_obsolete_options() -> None: os.environ[alternative] = os.environ[deprecated] else: print(f"Option '{alternative}' is not empty. Please unset '{deprecated}'") - exit(2) + sys.exit(2) # Check for deprecated identifiers in 'CIBW_BUILD' and 'CIBW_SKIP' options for option in ['CIBW_BUILD', 'CIBW_SKIP']: diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 6573aa82..3e17b1df 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -58,7 +58,7 @@ def build(options: BuildOptions) -> None: 'If you\'re building on Travis CI, add `services: [docker]` to your .travis.yml.' 'If you\'re building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml', file=sys.stderr) - exit(2) + sys.exit(2) assert options.manylinux_images is not None python_configurations = get_python_configurations(options.build_selector, options.architectures) @@ -138,12 +138,12 @@ def build(options: BuildOptions) -> None: which_python = docker.call(['which', 'python'], env=env, capture_output=True).strip() if PurePath(which_python) != python_bin / 'python': 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) - exit(1) + sys.exit(1) which_pip = docker.call(['which', 'pip'], env=env, capture_output=True).strip() if PurePath(which_pip) != python_bin / 'pip': 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) - exit(1) + sys.exit(1) if options.before_build: log.step('Running before_build...') @@ -233,7 +233,7 @@ def build(options: BuildOptions) -> None: except subprocess.CalledProcessError as error: log.error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}') troubleshoot(options.package_dir, error) - exit(1) + sys.exit(1) def troubleshoot(package_dir: Path, error: Exception) -> None: diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index de9cd15e..12677c49 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -157,7 +157,7 @@ def setup_python(python_configuration: PythonConfiguration, which_python = subprocess.check_output(['which', 'python'], env=env, universal_newlines=True).strip() if which_python != '/tmp/cibw_bin/python': 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) - exit(1) + sys.exit(1) # install pip & wheel call(['python', get_pip_script, *dependency_constraint_flags], env=env, cwd="/tmp") @@ -167,7 +167,7 @@ def setup_python(python_configuration: PythonConfiguration, which_pip = subprocess.check_output(['which', 'pip'], env=env, universal_newlines=True).strip() if which_pip != '/tmp/cibw_bin/pip': 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) - exit(1) + sys.exit(1) # Set MACOSX_DEPLOYMENT_TARGET to 10.9, if the user didn't set it. # CPython 3.5 defaults to 10.6, and pypy defaults to 10.7, causing @@ -301,4 +301,4 @@ def build(options: BuildOptions) -> None: log.build_end() except subprocess.CalledProcessError as error: log.error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}') - exit(1) + sys.exit(1) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index ef24fae9..5fec8c0b 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -153,7 +153,7 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain where_python = subprocess.check_output(['where', 'python'], env=env, universal_newlines=True).splitlines()[0].strip() if where_python != str(installation_path / 'python.exe'): 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) - exit(1) + sys.exit(1) # make sure pip is installed if not (installation_path / 'Scripts' / 'pip.exe').exists(): @@ -162,7 +162,7 @@ def setup_python(python_configuration: PythonConfiguration, dependency_constrain where_pip = subprocess.check_output(['where', 'pip'], env=env, universal_newlines=True).splitlines()[0].strip() if where_pip.strip() != str(installation_path / 'Scripts' / 'pip.exe'): 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) - exit(1) + sys.exit(1) log.step('Installing build tools...') @@ -330,4 +330,4 @@ def build(options: BuildOptions) -> None: log.build_end() except subprocess.CalledProcessError as error: log.error(f'Command {error.cmd} failed with code {error.returncode}. {error.stdout}') - exit(1) + sys.exit(1) From 03f83455d8ea51bdb7172e5ba884a6a84227837a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Jan 2021 08:29:10 -0500 Subject: [PATCH 11/27] Update dependencies (#546) --- .../resources/constraints-python27.txt | 2 +- .../resources/constraints-python35.txt | 2 +- .../resources/constraints-python36.txt | 4 ++-- .../resources/constraints-python37.txt | 4 ++-- .../resources/constraints-python38.txt | 4 ++-- .../resources/constraints-python39.txt | 4 ++-- cibuildwheel/resources/constraints.txt | 4 ++-- .../resources/pinned_docker_images.cfg | 18 +++++++++--------- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cibuildwheel/resources/constraints-python27.txt b/cibuildwheel/resources/constraints-python27.txt index b9456880..aca7c901 100644 --- a/cibuildwheel/resources/constraints-python27.txt +++ b/cibuildwheel/resources/constraints-python27.txt @@ -37,7 +37,7 @@ six==1.15.0 # virtualenv typing==3.7.4.3 # via importlib-resources -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via diff --git a/cibuildwheel/resources/constraints-python35.txt b/cibuildwheel/resources/constraints-python35.txt index 4ff43043..6790cdc2 100644 --- a/cibuildwheel/resources/constraints-python35.txt +++ b/cibuildwheel/resources/constraints-python35.txt @@ -18,7 +18,7 @@ importlib-resources==3.2.1 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via diff --git a/cibuildwheel/resources/constraints-python36.txt b/cibuildwheel/resources/constraints-python36.txt index 9ed440b7..ab2df3c3 100644 --- a/cibuildwheel/resources/constraints-python36.txt +++ b/cibuildwheel/resources/constraints-python36.txt @@ -20,7 +20,7 @@ six==1.15.0 # via virtualenv typing-extensions==3.7.4.3 # via importlib-metadata -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -34,5 +34,5 @@ zipp==3.4.0 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.2 +setuptools==51.3.3 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python37.txt b/cibuildwheel/resources/constraints-python37.txt index 258ab51d..04fe068c 100644 --- a/cibuildwheel/resources/constraints-python37.txt +++ b/cibuildwheel/resources/constraints-python37.txt @@ -18,7 +18,7 @@ six==1.15.0 # via virtualenv typing-extensions==3.7.4.3 # via importlib-metadata -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -30,5 +30,5 @@ zipp==3.4.0 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.2 +setuptools==51.3.3 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 6cb74781..991ebad6 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.2 +setuptools==51.3.3 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 6cb74781..991ebad6 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.2 +setuptools==51.3.3 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index 6cb74781..991ebad6 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -14,7 +14,7 @@ filelock==3.0.12 # via virtualenv six==1.15.0 # via virtualenv -virtualenv==20.3.0 +virtualenv==20.3.1 # via -r cibuildwheel/resources/constraints.in wheel==0.36.2 # via @@ -24,5 +24,5 @@ wheel==0.36.2 # The following packages are considered to be unsafe in a requirements file: pip==20.3.3 # via -r cibuildwheel/resources/constraints.in -setuptools==51.1.2 +setuptools==51.3.3 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg index 6aa7d846..2267f923 100644 --- a/cibuildwheel/resources/pinned_docker_images.cfg +++ b/cibuildwheel/resources/pinned_docker_images.cfg @@ -1,22 +1,22 @@ [x86_64] -manylinux1 = quay.io/pypa/manylinux1_x86_64:2020-12-31-84e1e23 -manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2020-12-31-4928808 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2020-12-31-56195b3 +manylinux1 = quay.io/pypa/manylinux1_x86_64:2021-01-11-28ab838 +manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2021-01-12-ff3830d +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2021-01-12-c8250d8 [i686] -manylinux1 = quay.io/pypa/manylinux1_i686:2020-12-31-84e1e23 -manylinux2010 = quay.io/pypa/manylinux2010_i686:2020-12-31-4928808 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2020-12-31-56195b3 +manylinux1 = quay.io/pypa/manylinux1_i686:2021-01-11-28ab838 +manylinux2010 = quay.io/pypa/manylinux2010_i686:2021-01-12-ff3830d +manylinux2014 = quay.io/pypa/manylinux2014_i686:2021-01-12-c8250d8 [pypy_x86_64] manylinux2010 = pypywheels/manylinux2010-pypy_x86_64:2020-12-11-f1e0e80 [aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2020-12-31-56195b3 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2021-01-12-c8250d8 [ppc64le] -manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2020-12-31-56195b3 +manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2021-01-12-c8250d8 [s390x] -manylinux2014 = quay.io/pypa/manylinux2014_s390x:2020-12-31-56195b3 +manylinux2014 = quay.io/pypa/manylinux2014_s390x:2021-01-12-c8250d8 From 4a02410632797e1a59b00739f27ae2a3c6230128 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 20 Jan 2021 18:16:15 -0500 Subject: [PATCH 12/27] fix: action still runs if setup-python is not called (#542) * fix: action works if setup-python is missed * fix: two steps * fix: combine again * fix: use pipx for action --- action.yml | 10 ++++++---- docs/faq.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/action.yml b/action.yml index 9d23e023..6429ff78 100644 --- a/action.yml +++ b/action.yml @@ -19,8 +19,10 @@ runs: # This needs powershell-core because github.action path is a Windows-style # path on Windows. Powershell-core understands both types of paths. - - run: python -m pip install ${{ github.action_path }} + - run: > + pipx run + --spec ${{ github.action_path }} + cibuildwheel + ${{ inputs.package-dir }} + --output-dir ${{ inputs.output-dir }} shell: pwsh - - - run: python -m cibuildwheel ${{ inputs.package-dir }} --output-dir ${{ inputs.output-dir }} - shell: bash diff --git a/docs/faq.md b/docs/faq.md index 07dc171e..ac7c0824 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -116,7 +116,7 @@ If you use GitHub Actions for builds, you can use cibuildwheel as an action: uses: joerick/cibuildwheel@v1.7.4 ``` -This is a composite step that just installs and runs cibuildwheel. You can set command-line options as `with:` parameters, and use `env:` as normal. +This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal. Then, your `dependabot.yml` file could look like this: From 6d92dbc5557f72147d02b7cbee62a25c5fa69ac0 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 21 Jan 2021 00:30:47 +0000 Subject: [PATCH 13/27] docs: massive props! (#549) Adding some long overdue 'props' to the README to thank @Czaki and @mattip for their contributions to the project! (thanks @henryiii for the reminder!) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2159843b..cac097a7 100644 --- a/README.md +++ b/README.md @@ -753,6 +753,8 @@ Massive props also to- - @zfrenchee for [help debugging many issues](https://github.com/joerick/cibuildwheel/issues/2) - @lelit for some great bug reports and [contributions](https://github.com/joerick/cibuildwheel/pull/73) - @mayeut for a [phenomenal PR](https://github.com/joerick/cibuildwheel/pull/71) patching Python itself for better compatibility! +- @czaki for being a super-contributor over many PRs and helping out with countless issues! +- @mattip for his help with adding PyPy support to cibuildwheel See also ======== From fba65a60083f66a0abb570f8ee6390cb09fb8abc Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 20 Jan 2021 21:22:48 -0500 Subject: [PATCH 14/27] feat: CIBW_TEST_SKIP (#537) * feat: CIBW_TEST_SKIP * refactor: adjust a bit and add docs * fix: typo in name * docs: Update docs/options.md Co-authored-by: Joe Rickerby * docs: add README line from markdown Co-authored-by: Joe Rickerby --- README.md | 1 + cibuildwheel/__main__.py | 6 +++++- cibuildwheel/linux.py | 2 +- cibuildwheel/macos.py | 2 +- cibuildwheel/util.py | 23 +++++++++++++++++++---- cibuildwheel/windows.py | 2 +- docs/options.md | 12 ++++++++++++ 7 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cac097a7..3c537c99 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Options | | [`CIBW_BEFORE_TEST`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel | | | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests | | | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require | +| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds | | **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel | diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 96f6a5c8..f5a7ace9 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -18,6 +18,7 @@ from cibuildwheel.util import ( BuildOptions, BuildSelector, DependencyConstraints, + TestSelector, Unbuffered, detect_ci_provider, resources_dir, @@ -140,6 +141,7 @@ def main() -> None: assert_never(platform) build_config, skip_config = os.environ.get('CIBW_BUILD', '*'), os.environ.get('CIBW_SKIP', '') + test_skip = os.environ.get('CIBW_TEST_SKIP', '') environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='') before_all = get_option_from_environment('CIBW_BEFORE_ALL', platform=platform, default='') before_build = get_option_from_environment('CIBW_BEFORE_BUILD', platform=platform) @@ -151,7 +153,8 @@ def main() -> None: test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='') build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='') - build_selector = BuildSelector(build_config, skip_config) + build_selector = BuildSelector(build_config=build_config, skip_config=skip_config) + test_selector = TestSelector(skip_config=test_skip) try: environment = parse_environment(environment_config) @@ -236,6 +239,7 @@ def main() -> None: before_all=before_all, build_verbosity=build_verbosity, build_selector=build_selector, + test_selector=test_selector, repair_command=repair_command, environment=environment, dependency_constraints=dependency_constraints, diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 3e17b1df..1a104d7c 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -183,7 +183,7 @@ def build(options: BuildOptions) -> None: repaired_wheels = docker.glob(repaired_wheel_dir, '*.whl') - if options.test_command: + if options.test_command and options.test_selector(config.identifier): log.step('Testing wheel...') # set up a virtual environment to install and test from, to make sure diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 12677c49..66b41b3f 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -252,7 +252,7 @@ def build(options: BuildOptions) -> None: repaired_wheel = next(repaired_wheel_dir.glob('*.whl')) - if options.test_command: + if options.test_command and options.test_selector(config.identifier): log.step('Testing wheel...') # set up a virtual environment to install and test from, to make sure # there are no dependencies that were pulled in at build time. diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index bfc54d5a..33435c16 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -57,8 +57,13 @@ def read_python_configs(config: PlatformName) -> List[Dict[str, str]]: return results -class BuildSelector: - def __init__(self, build_config: str, skip_config: str): +class IdentifierSelector: + """ + This class holds a set of build/skip patterns. You call an instance with a + build identifier, and it returns True if that identifier should be + included. + """ + def __init__(self, *, build_config: str, skip_config: str): self.build_patterns = build_config.split() self.skip_patterns = skip_config.split() @@ -72,9 +77,18 @@ class BuildSelector: def __repr__(self) -> str: if not self.skip_patterns: - return f'BuildSelector({" ".join(self.build_patterns)!r})' + return f'{self.__class__.__name__}({" ".join(self.build_patterns)!r})' else: - return f'BuildSelector({" ".join(self.build_patterns)!r} - {" ".join(self.skip_patterns)!r})' + return f'{self.__class__.__name__}({" ".join(self.build_patterns)!r} - {" ".join(self.skip_patterns)!r})' + + +class BuildSelector(IdentifierSelector): + pass + + +class TestSelector(IdentifierSelector): + def __init__(self, *, skip_config: str): + super().__init__(build_config="*", skip_config=skip_config) # Taken from https://stackoverflow.com/a/107717 @@ -217,6 +231,7 @@ class BuildOptions(NamedTuple): manylinux_images: Optional[Dict[str, str]] dependency_constraints: Optional[DependencyConstraints] test_command: Optional[str] + test_selector: TestSelector before_test: Optional[str] test_requires: List[str] test_extras: str diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 5fec8c0b..bd929358 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -277,7 +277,7 @@ def build(options: BuildOptions) -> None: repaired_wheel = next(repaired_wheel_dir.glob('*.whl')) - if options.test_command: + if options.test_command and options.test_selector(config.identifier): log.step('Testing wheel...') # set up a virtual environment to install and test from, to make sure # there are no dependencies that were pulled in at build time. diff --git a/docs/options.md b/docs/options.md index a65a5796..e8b72fdd 100644 --- a/docs/options.md +++ b/docs/options.md @@ -518,6 +518,18 @@ Platform-specific variants also available:
CIBW_TEST_EXTRAS: test,qt ``` +### `CIBW_TEST_SKIP` {: #test-skip} +> Skip running tests on some builds + +This will skip testing on any identifiers that match the given skip patterns (see [`CIBW_SKIP`](#build-skip)). This can be used to mask out tests for wheels that have missing dependencies upstream that are slow or hard to build, or to mask up slow tests on emulated architectures. + +#### Examples + +```yaml +# Will avoid testing on emulated architectures +CIBW_TEST_SKIP: "*-manylinux_{aarch64,ppc64le,s390x}" +``` + ## Other From 98b5ff447d5484a133dbf506af09a53826d9ca0f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 21 Jan 2021 12:02:58 -0500 Subject: [PATCH 15/27] docs: update for some new packages --- README.md | 109 +++++++++++++++++++++++------------------ docs/data/projects.yml | 26 ++++++++++ 2 files changed, 87 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 3c537c99..2bccd6b7 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Options |---|--------|-------------| | **Build selection** | [`CIBW_PLATFORM`](https://cibuildwheel.readthedocs.io/en/stable/options/#platform) | Override the auto-detected target platform | | | [`CIBW_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip)
[`CIBW_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) | Choose the Python versions to build | +| | [`CIBW_ARCHS_LINUX`](https://cibuildwheel.readthedocs.io/en/stable/options/#archs) | Build non-native architectures | | **Build customization** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build | | | [`CIBW_BEFORE_ALL`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. | | | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build | @@ -131,19 +132,21 @@ Here are some repos that use cibuildwheel. |-----------------------------------|----|----|:------| | [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. | | [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions | +| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | MyPyC, the compiled component of MyPy. | | [psutil][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Cross-platform lib for process and system monitoring in Python | -| [twisted-iocpsupport][] | ![github icon][] | ![windows icon][] | A submodule of Twisted that hooks into native C APIs using Cython. | | [scikit-image][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Image processing library. Uses cibuildwheel to build and test a project that uses Cython with platform-native code. | +| [twisted-iocpsupport][] | ![github icon][] | ![windows icon][] | A submodule of Twisted that hooks into native C APIs using Cython. | | [websockets][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Library for building WebSocket servers and clients. Mostly written in Python, with a small C 'speedups' extension module. | | [pyzmq][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython and CFFI. | | [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. | | [numexpr][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast numerical array expression evaluator for Python, NumPy, PyTables, pandas, bcolz and more | -| [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python | | [h5py][] | ![azurepipelines icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | HDF5 for Python -- The h5py package is a Pythonic interface to the HDF5 binary data format. | +| [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python | | [River][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | 🌊 Online machine learning in Python | | [PyAV][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Pythonic bindings for FFmpeg's libraries. | | [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI | | [PyTables][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data | +| [ruptures][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. | | [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python | | [google neuroglancer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data | | [AutoPy][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. | @@ -151,14 +154,15 @@ Here are some repos that use cibuildwheel. | [Parselmouth][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. | | [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] ![appveyor icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson | | [Rtree][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Rtree: spatial index for Python GIS ¢ | -| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing | | [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python | +| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing | | [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a CMake-based build system | | [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system | | [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub | | [iminuit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Jupyter-friendly Python interface for C++ MINUIT2 | | [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq | | [matrixprofile][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python 3 library making time series data mining tasks, utilizing matrix profile algorithms, accessible to everyone. | +| [CTranslate2][] | ![github icon][] | ![apple icon][] ![linux icon][] | Includes libraries from the [Intel oneAPI toolkit](https://software.intel.com/content/www/us/en/develop/tools/oneapi/base-toolkit.html). The Linux wheels also include CUDA libraries for GPU execution. | | [Tokenizer][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] | Fast and customizable text tokenization library with BPE and SentencePiece support | | [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. | | [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. | @@ -169,24 +173,27 @@ Here are some repos that use cibuildwheel. | [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy | | [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms | | [pyinstrument_cext][] | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies | +| [Imagecodecs (fork)][] | ![github icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. | | [xmlstarlet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. | | [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 | [scikit-learn]: https://github.com/scikit-learn/scikit-learn [Matplotlib]: https://github.com/matplotlib/matplotlib +[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels [psutil]: https://github.com/giampaolo/psutil -[twisted-iocpsupport]: https://github.com/twisted/twisted-iocpsupport [scikit-image]: https://github.com/scikit-image/scikit-image +[twisted-iocpsupport]: https://github.com/twisted/twisted-iocpsupport [websockets]: https://github.com/aaugustin/websockets [pyzmq]: https://github.com/zeromq/pyzmq [aiortc]: https://github.com/aiortc/aiortc [numexpr]: https://github.com/pydata/numexpr -[coverage.py]: https://github.com/nedbat/coveragepy [h5py]: https://github.com/h5py/h5py +[coverage.py]: https://github.com/nedbat/coveragepy [River]: https://github.com/online-ml/river [PyAV]: https://github.com/PyAV-Org/PyAV [Dependency Injector]: https://github.com/ets-labs/python-dependency-injector [PyTables]: https://github.com/PyTables/PyTables +[ruptures]: https://github.com/deepcharles/ruptures [aioquic]: https://github.com/aiortc/aioquic [google neuroglancer]: https://github.com/google/neuroglancer [AutoPy]: https://github.com/autopilot-rs/autopy @@ -194,14 +201,15 @@ Here are some repos that use cibuildwheel. [Parselmouth]: https://github.com/YannickJadoul/Parselmouth [python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson [Rtree]: https://github.com/Toblerity/rtree -[cyvcf2]: https://github.com/brentp/cyvcf2 [KDEpy]: https://github.com/tommyod/KDEpy +[cyvcf2]: https://github.com/brentp/cyvcf2 [pybind11 cmake_example]: https://github.com/pybind/cmake_example [pybind11 python_example]: https://github.com/pybind/python_example [dd-trace-py]: https://github.com/DataDog/dd-trace-py [iminuit]: https://github.com/scikit-hep/iminuit [jq.py]: https://github.com/mwilliamson/jq.py [matrixprofile]: https://github.com/matrix-profile-foundation/matrixprofile +[CTranslate2]: https://github.com/OpenNMT/CTranslate2 [Tokenizer]: https://github.com/OpenNMT/Tokenizer [bx-python]: https://github.com/bxlab/bx-python [boost-histogram]: https://github.com/scikit-hep/boost-histogram @@ -212,6 +220,7 @@ Here are some repos that use cibuildwheel. [numpythia]: https://github.com/scikit-hep/numpythia [fathon]: https://github.com/stfbnc/fathon [pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext +[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build [xmlstarlet]: https://github.com/dimitern/xmlstarlet [pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example @@ -225,52 +234,56 @@ Here are some repos that use cibuildwheel. [apple icon]: docs/data/readme_icons/apple.svg [linux icon]: docs/data/readme_icons/linux.svg - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + -> Add your repo here! Send a PR, adding your information to `docs/data/projects.yml`. +> Add your repo here! Let us know on [GitHub Discussions](https://github.com/joerick/cibuildwheel/discussions/485), or send a PR, adding your information to `docs/data/projects.yml`. > > I'd like to include notes here to indicate why an example might be interesting to cibuildwheel users - the styles/technologies/techniques used in each. Please include that in future additions! diff --git a/docs/data/projects.yml b/docs/data/projects.yml index 8317e674..52bd600a 100644 --- a/docs/data/projects.yml +++ b/docs/data/projects.yml @@ -260,3 +260,29 @@ gh: brentp/cyvcf2 ci: [github] os: [apple, linux] + +- name: CTranslate2 + gh: OpenNMT/CTranslate2 + ci: [github] + os: [apple, linux] + notes: Includes libraries from the [Intel oneAPI toolkit](https://software.intel.com/content/www/us/en/develop/tools/oneapi/base-toolkit.html). The Linux wheels also include CUDA libraries for GPU execution. + +- name: MyPy + gh: mypyc/mypy_mypyc-wheels + stars: python/mypy + ci: [github] + os: [apple, linux, windows] + notes: MyPyC, the compiled component of MyPy. + +- name: Imagecodecs (fork) + gh: czaki/imagecodecs_build + stars: cgohlke/imagecodecs + ci: [github] + os: [apple, linux] + notes: Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. + +- name: ruptures + gh: deepcharles/ruptures + ci: [github] + os: [apple, linux, windows] + notes: Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. From da19ac30e6d13552d85a31cc33286ab334454714 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 21 Jan 2021 12:29:02 -0500 Subject: [PATCH 16/27] docs: fix build info --- README.md | 8 ++++---- docs/data/projects.yml | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2bccd6b7..5c6b5861 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ Here are some repos that use cibuildwheel. | [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy | | [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms | | [pyinstrument_cext][] | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies | -| [Imagecodecs (fork)][] | ![github icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. | +| [Imagecodecs (fork)][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. | | [xmlstarlet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. | | [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 | @@ -234,7 +234,7 @@ Here are some repos that use cibuildwheel. [apple icon]: docs/data/readme_icons/apple.svg [linux icon]: docs/data/readme_icons/linux.svg - + @@ -243,7 +243,7 @@ Here are some repos that use cibuildwheel. - + @@ -271,7 +271,7 @@ Here are some repos that use cibuildwheel. - + diff --git a/docs/data/projects.yml b/docs/data/projects.yml index 52bd600a..37a3114d 100644 --- a/docs/data/projects.yml +++ b/docs/data/projects.yml @@ -277,7 +277,8 @@ - name: Imagecodecs (fork) gh: czaki/imagecodecs_build stars: cgohlke/imagecodecs - ci: [github] + ci: [azurepipelines] + ci_config: azure-pipelines.yaml os: [apple, linux] notes: Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. From 27a0053386bc73ba340b5bcb9f44fcea42ce6875 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 18 Jan 2021 00:09:11 -0500 Subject: [PATCH 17/27] fix: error if selector is empty --- cibuildwheel/__main__.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index f5a7ace9..8bac5e5d 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -5,7 +5,7 @@ import textwrap import traceback from configparser import ConfigParser from pathlib import Path -from typing import Any, Dict, List, Optional, Set, overload +from typing import Dict, List, Optional, Set, Union, overload import cibuildwheel import cibuildwheel.linux @@ -194,8 +194,14 @@ def main() -> None: archs_config_str = get_option_from_environment('CIBW_ARCHS', platform=platform, default='auto') archs = Architecture.parse_config(archs_config_str, platform=platform) + identifiers = get_build_identifiers(platform, build_selector, archs) + if not identifiers: + print("ERROR: No build identifiers selected!") + sys.exit(3) + if args.print_build_identifiers: - print_build_identifiers(platform, build_selector, archs) + for identifier in identifiers: + print(identifier) sys.exit(0) manylinux_images: Optional[Dict[str, str]] = None @@ -311,20 +317,22 @@ def print_preamble(platform: str, build_options: BuildOptions) -> None: print('\nHere we go!\n') -def print_build_identifiers( - platform: str, build_selector: BuildSelector, architectures: Set[Architecture] -) -> None: - - python_configurations: List[Any] = [] +def get_build_identifiers( + platform: PlatformName, build_selector: BuildSelector, architectures: Set[Architecture] +) -> List[str]: + python_configurations: Union[List[cibuildwheel.linux.PythonConfiguration], + List[cibuildwheel.windows.PythonConfiguration], + List[cibuildwheel.macos.PythonConfiguration]] if platform == 'linux': python_configurations = cibuildwheel.linux.get_python_configurations(build_selector, architectures) elif platform == 'windows': python_configurations = cibuildwheel.windows.get_python_configurations(build_selector, architectures) elif platform == 'macos': python_configurations = cibuildwheel.macos.get_python_configurations(build_selector) + else: + assert_never(platform) - for config in python_configurations: - print(config.identifier) + return [config.identifier for config in python_configurations] def detect_warnings(platform: str, build_options: BuildOptions) -> List[str]: From 61b94434b287b4c432240787657a6aa8f780ebca Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 18 Jan 2021 01:03:15 -0500 Subject: [PATCH 18/27] fix: error if build selectors are empty --- cibuildwheel/__main__.py | 7 ++++--- pyproject.toml | 6 ++++++ unit_test/main_tests/conftest.py | 9 +++++++++ unit_test/main_tests/main_options_test.py | 23 ++++++++++++++++++++++ unit_test/main_tests/main_platform_test.py | 5 +++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 8bac5e5d..068897dc 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -195,9 +195,6 @@ def main() -> None: archs = Architecture.parse_config(archs_config_str, platform=platform) identifiers = get_build_identifiers(platform, build_selector, archs) - if not identifiers: - print("ERROR: No build identifiers selected!") - sys.exit(3) if args.print_build_identifiers: for identifier in identifiers: @@ -269,6 +266,10 @@ def main() -> None: else: assert_never(platform) + if not identifiers: + print("ERROR: No build identifiers selected!") + sys.exit(3) + def detect_obsolete_options() -> None: # Check the old 'MANYLINUX1_*_IMAGE' options diff --git a/pyproject.toml b/pyproject.toml index 285367ae..c05fec7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,9 @@ requires = [ ] build-backend = "setuptools.build_meta" + + +[tool.pytest.ini_options] +markers = [ + "allow_empty(platform): Allow main tests on this platform(s) to be empty", +] diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index 1122a88c..c9ae6e8a 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -59,6 +59,15 @@ def fake_package_dir(monkeypatch): def platform(request, monkeypatch): platform_value = request.param monkeypatch.setenv('CIBW_PLATFORM', platform_value) + + marker = request.node.get_closest_marker('allow_empty') + if marker is not None and (len(marker.args) == 0 or platform_value in marker.args): + def pass_exit(val: int): + if val not in {3}: + sys.exit(val) + + monkeypatch.setattr(sys, 'exit', pass_exit) + return platform_value diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index f28b1ed4..fef74203 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -11,6 +11,7 @@ from cibuildwheel.util import BuildSelector # CIBW_PLATFORM is tested in main_platform_test.py +@pytest.mark.allow_empty('windows') def test_output_dir(platform, intercepted_build_args, monkeypatch): OUTPUT_DIR = Path('some_output_dir') @@ -21,12 +22,14 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch): assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR +@pytest.mark.allow_empty('windows') def test_output_dir_default(platform, intercepted_build_args, monkeypatch): main() assert intercepted_build_args.args[0].output_dir == Path('wheelhouse') +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('also_set_environment', [False, True]) def test_output_dir_argument(also_set_environment, platform, intercepted_build_args, monkeypatch): OUTPUT_DIR = Path('some_output_dir') @@ -40,6 +43,7 @@ def test_output_dir_argument(also_set_environment, platform, intercepted_build_a assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR +@pytest.mark.allow_empty def test_build_selector(platform, intercepted_build_args, monkeypatch): BUILD = 'some build* *-selector' SKIP = 'some skip* *-selector' @@ -57,6 +61,16 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch): # Unit tests for BuildSelector are in build_selector_test.py +def test_empty_selector(platform, intercepted_build_args, monkeypatch): + monkeypatch.setenv('CIBW_SKIP', '*') + + with pytest.raises(SystemExit) as e: + main() + + assert e.value.code == 3 + + +@pytest.mark.allow_empty @pytest.mark.parametrize('architecture, image, full_image', [ ('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'), ('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'), @@ -100,6 +114,7 @@ def get_default_repair_command(platform): raise ValueError('Unknown platform', platform) +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('repair_command', [None, 'repair', 'repair -w {dest_dir} {wheel}']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_repair_command(repair_command, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -116,6 +131,7 @@ def test_repair_command(repair_command, platform_specific, platform, intercepted assert intercepted_build_args.args[0].repair_command == expected_repair +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('environment', [ {}, {'something': 'value'}, @@ -137,6 +153,7 @@ def test_environment(environment, platform_specific, platform, intercepted_build assert intercepted_environment.as_dictionary(prev_environment={}) == environment +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_requires', [None, 'requirement other_requirement']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_requires(test_requires, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -152,6 +169,7 @@ def test_test_requires(test_requires, platform_specific, platform, intercepted_b assert intercepted_build_args.args[0].test_requires == (test_requires or '').split() +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_extras', [None, 'extras']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_extras(test_extras, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -167,6 +185,7 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build assert intercepted_build_args.args[0].test_extras == ('[' + test_extras + ']' if test_extras else '') +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_command', [None, 'test --command']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_command(test_command, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -182,6 +201,7 @@ def test_test_command(test_command, platform_specific, platform, intercepted_bui assert intercepted_build_args.args[0].test_command == test_command +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('before_build', [None, 'before --build']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_before_build(before_build, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -197,6 +217,7 @@ def test_before_build(before_build, platform_specific, platform, intercepted_bui assert intercepted_build_args.args[0].before_build == before_build +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('build_verbosity', [None, 0, 2, -2, 4, -4]) @pytest.mark.parametrize('platform_specific', [False, True]) def test_build_verbosity(build_verbosity, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -213,6 +234,7 @@ def test_build_verbosity(build_verbosity, platform_specific, platform, intercept assert intercepted_build_args.args[0].build_verbosity == expected_verbosity +@pytest.mark.allow_empty @pytest.mark.parametrize('option_name', ['CIBW_BUILD', 'CIBW_SKIP']) @pytest.mark.parametrize('option_value, build_selector_patterns', [ ('*-manylinux1_*', ['*-manylinux_*']), @@ -234,6 +256,7 @@ def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_n assert intercepted_build_selector.skip_patterns == build_selector_patterns +@pytest.mark.allow_empty @pytest.mark.parametrize('before_all', ["", None, 'test text']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_before_all(before_all, platform_specific, platform, intercepted_build_args, monkeypatch): diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 9b7c06fe..6445f331 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -52,6 +52,7 @@ def test_unknown_platform(monkeypatch, capsys): assert 'cibuildwheel: Unsupported platform: nonexistent' in err +@pytest.mark.allow_empty('windows') def test_platform_argument(platform, intercepted_build_args, monkeypatch): monkeypatch.setenv('CIBW_PLATFORM', 'nonexistent') monkeypatch.setattr(sys, 'argv', sys.argv + ['--platform', platform]) @@ -61,12 +62,14 @@ def test_platform_argument(platform, intercepted_build_args, monkeypatch): assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR +@pytest.mark.allow_empty('windows') def test_platform_environment(platform, intercepted_build_args, monkeypatch): main() assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR +@pytest.mark.allow_empty('windows') def test_archs_default(platform, intercepted_build_args, monkeypatch): monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') @@ -79,6 +82,7 @@ def test_archs_default(platform, intercepted_build_args, monkeypatch): assert build_options.architectures == {Architecture.x86_64} +@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('use_env_var', [False, True]) def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_var): monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') @@ -112,6 +116,7 @@ def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): assert build_options.architectures == {Architecture.x86_64} +@pytest.mark.allow_empty('windows') def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') monkeypatch.setenv('CIBW_ARCHS', 'native') From c90c7666b929f96e4c02916ebd54a311f21f1c98 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 18 Jan 2021 09:54:32 -0500 Subject: [PATCH 19/27] tests: fix arch for platform tests --- unit_test/main_tests/conftest.py | 6 ++++++ unit_test/main_tests/main_options_test.py | 12 ------------ unit_test/main_tests/main_platform_test.py | 2 -- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index c9ae6e8a..c70ff98e 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -1,3 +1,4 @@ +import platform as platform_module import subprocess import sys from pathlib import Path @@ -60,6 +61,11 @@ def platform(request, monkeypatch): platform_value = request.param monkeypatch.setenv('CIBW_PLATFORM', platform_value) + if platform_value == 'windows': + monkeypatch.setattr(platform_module, 'machine', lambda: 'AMD64') + else: + monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') + marker = request.node.get_closest_marker('allow_empty') if marker is not None and (len(marker.args) == 0 or platform_value in marker.args): def pass_exit(val: int): diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index fef74203..345cfe2a 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -11,7 +11,6 @@ from cibuildwheel.util import BuildSelector # CIBW_PLATFORM is tested in main_platform_test.py -@pytest.mark.allow_empty('windows') def test_output_dir(platform, intercepted_build_args, monkeypatch): OUTPUT_DIR = Path('some_output_dir') @@ -22,14 +21,12 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch): assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR -@pytest.mark.allow_empty('windows') def test_output_dir_default(platform, intercepted_build_args, monkeypatch): main() assert intercepted_build_args.args[0].output_dir == Path('wheelhouse') -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('also_set_environment', [False, True]) def test_output_dir_argument(also_set_environment, platform, intercepted_build_args, monkeypatch): OUTPUT_DIR = Path('some_output_dir') @@ -70,7 +67,6 @@ def test_empty_selector(platform, intercepted_build_args, monkeypatch): assert e.value.code == 3 -@pytest.mark.allow_empty @pytest.mark.parametrize('architecture, image, full_image', [ ('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64:*'), ('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64:*'), @@ -114,7 +110,6 @@ def get_default_repair_command(platform): raise ValueError('Unknown platform', platform) -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('repair_command', [None, 'repair', 'repair -w {dest_dir} {wheel}']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_repair_command(repair_command, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -131,7 +126,6 @@ def test_repair_command(repair_command, platform_specific, platform, intercepted assert intercepted_build_args.args[0].repair_command == expected_repair -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('environment', [ {}, {'something': 'value'}, @@ -153,7 +147,6 @@ def test_environment(environment, platform_specific, platform, intercepted_build assert intercepted_environment.as_dictionary(prev_environment={}) == environment -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_requires', [None, 'requirement other_requirement']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_requires(test_requires, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -169,7 +162,6 @@ def test_test_requires(test_requires, platform_specific, platform, intercepted_b assert intercepted_build_args.args[0].test_requires == (test_requires or '').split() -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_extras', [None, 'extras']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_extras(test_extras, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -185,7 +177,6 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build assert intercepted_build_args.args[0].test_extras == ('[' + test_extras + ']' if test_extras else '') -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('test_command', [None, 'test --command']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_test_command(test_command, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -201,7 +192,6 @@ def test_test_command(test_command, platform_specific, platform, intercepted_bui assert intercepted_build_args.args[0].test_command == test_command -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('before_build', [None, 'before --build']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_before_build(before_build, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -217,7 +207,6 @@ def test_before_build(before_build, platform_specific, platform, intercepted_bui assert intercepted_build_args.args[0].before_build == before_build -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('build_verbosity', [None, 0, 2, -2, 4, -4]) @pytest.mark.parametrize('platform_specific', [False, True]) def test_build_verbosity(build_verbosity, platform_specific, platform, intercepted_build_args, monkeypatch): @@ -256,7 +245,6 @@ def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_n assert intercepted_build_selector.skip_patterns == build_selector_patterns -@pytest.mark.allow_empty @pytest.mark.parametrize('before_all', ["", None, 'test text']) @pytest.mark.parametrize('platform_specific', [False, True]) def test_before_all(before_all, platform_specific, platform, intercepted_build_args, monkeypatch): diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 6445f331..0f277afe 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -52,7 +52,6 @@ def test_unknown_platform(monkeypatch, capsys): assert 'cibuildwheel: Unsupported platform: nonexistent' in err -@pytest.mark.allow_empty('windows') def test_platform_argument(platform, intercepted_build_args, monkeypatch): monkeypatch.setenv('CIBW_PLATFORM', 'nonexistent') monkeypatch.setattr(sys, 'argv', sys.argv + ['--platform', platform]) @@ -62,7 +61,6 @@ def test_platform_argument(platform, intercepted_build_args, monkeypatch): assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR -@pytest.mark.allow_empty('windows') def test_platform_environment(platform, intercepted_build_args, monkeypatch): main() From eb9850dbde2613449bb43938020d23e3ee934339 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 18 Jan 2021 16:44:47 -0500 Subject: [PATCH 20/27] feat: support Windows workaround on Travis CI --- cibuildwheel/windows.py | 3 ++- docs/setup.md | 2 +- test/test_cpp_standards.py | 2 +- test/test_dependency_versions.py | 6 ++++++ test/utils.py | 5 +++-- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index bd929358..5e8c1683 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -70,7 +70,8 @@ def get_python_configurations( '64': Architecture.AMD64, } - if IS_RUNNING_ON_TRAVIS: + custom_compiler = os.environ.get('DISTUTILS_USE_SDK') and os.environ.get('MSSdk') + if IS_RUNNING_ON_TRAVIS and not custom_compiler: # cannot install VCForPython27.msi which is needed for compiling C software # try with (and similar): msiexec /i VCForPython27.msi ALLUSERS=1 ACCEPT=YES /passive python_configurations = [c for c in python_configurations if not c.version.startswith('2.7')] diff --git a/docs/setup.md b/docs/setup.md index 282edde9..c86ebf13 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -44,7 +44,7 @@ To build Linux, Mac, and Windows wheels on Travis CI, create a `.travis.yml` fil {% include "../examples/travis-ci-minimal.yml" %} ``` -Note that building Windows Python 2.7 wheels on Travis is unsupported. +Note that building Windows Python 2.7 wheels on Travis is unsupported unless using a newer compiler [via a workaround](cpp-standards.md). Commit this file, enable building of your repo on Travis CI, and push. diff --git a/test/test_cpp_standards.py b/test/test_cpp_standards.py index 0ecf196d..8ab5becd 100644 --- a/test/test_cpp_standards.py +++ b/test/test_cpp_standards.py @@ -202,7 +202,7 @@ def test_cpp17_py27_modern_msvc_workaround(tmp_path): add_env_x64['CIBW_BUILD'] = 'cp27-win_amd64' actual_wheels += utils.cibuildwheel_run(project_dir, add_env=add_env_x64) - expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', filter_27=False) if 'cp27-cp27m-win' in w or 'pp27-pypy_73-win32' in w] diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index 07d22e24..1b415bb4 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -52,6 +52,9 @@ def test_pinned_versions(tmp_path, python_version): if utils.platform == 'linux': pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead') + if utils.IS_WINDOWS_RUNNING_ON_TRAVIS and python_version == '2.7': + pytest.skip('Windows + Travis CI requires a workaround') + project_dir = tmp_path / 'project' project_with_expected_version_checks.generate(project_dir) @@ -114,6 +117,9 @@ def test_dependency_constraints_file(tmp_path, python_version): if utils.platform == 'linux': pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead') + if utils.IS_WINDOWS_RUNNING_ON_TRAVIS and python_version == '2.7': + pytest.skip('Windows + Travis CI requires a workaround') + project_dir = tmp_path / 'project' project_with_expected_version_checks.generate(project_dir) diff --git a/test/utils.py b/test/utils.py index bae3853b..4fcdba47 100644 --- a/test/utils.py +++ b/test/utils.py @@ -76,7 +76,8 @@ def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, outp def expected_wheels(package_name, package_version, manylinux_versions=None, - macosx_deployment_target='10.9', machine_arch=None): + macosx_deployment_target='10.9', machine_arch=None, *, + filter_27=IS_WINDOWS_RUNNING_ON_TRAVIS): ''' Returns a list of expected wheels from a run of cibuildwheel. ''' @@ -134,7 +135,7 @@ def expected_wheels(package_name, package_version, manylinux_versions=None, for platform_tag in platform_tags: wheels.append(f'{package_name}-{package_version}-{python_abi_tag}-{platform_tag}.whl') - if IS_WINDOWS_RUNNING_ON_TRAVIS: + if filter_27: # Python 2.7 isn't supported on Travis. wheels = [w for w in wheels if '-cp27-' not in w and '-pp2' not in w] From 5762aa5e3bb77661c22817ab616e40d7de316441 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 19 Jan 2021 20:52:06 -0500 Subject: [PATCH 21/27] feat: allow-empty and simpler testing --- cibuildwheel/__main__.py | 8 ++++++-- pyproject.toml | 6 ------ unit_test/main_tests/conftest.py | 17 ++++++++--------- unit_test/main_tests/main_options_test.py | 6 ++---- unit_test/main_tests/main_platform_test.py | 14 ++++++++------ 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 068897dc..bcdfef8c 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -95,6 +95,9 @@ def main() -> None: parser.add_argument('--print-build-identifiers', action='store_true', help='Print the build identifiers matched by the current invocation and exit.') + parser.add_argument('--allow-empty', + action='store_true', + help='Do not report an error code if the build does not match any wheels.') args = parser.parse_args() @@ -267,8 +270,9 @@ def main() -> None: assert_never(platform) if not identifiers: - print("ERROR: No build identifiers selected!") - sys.exit(3) + print(f'cibuildwheel: No build identifiers selected: {build_selector}', file=sys.stderr) + if not args.allow_empty: + sys.exit(3) def detect_obsolete_options() -> None: diff --git a/pyproject.toml b/pyproject.toml index c05fec7d..285367ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,9 +5,3 @@ requires = [ ] build-backend = "setuptools.build_meta" - - -[tool.pytest.ini_options] -markers = [ - "allow_empty(platform): Allow main tests on this platform(s) to be empty", -] diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index c70ff98e..3ce025a0 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -52,8 +52,15 @@ def fake_package_dir(monkeypatch): else: return real_path_exists(path) + args = ['cibuildwheel', str(MOCK_PACKAGE_DIR)] monkeypatch.setattr(Path, 'exists', mock_path_exists) - monkeypatch.setattr(sys, 'argv', ['cibuildwheel', str(MOCK_PACKAGE_DIR)]) + monkeypatch.setattr(sys, 'argv', args) + return args + + +@pytest.fixture +def allow_empty(request, monkeypatch, fake_package_dir): + monkeypatch.setattr(sys, 'argv', fake_package_dir + ['--allow-empty']) @pytest.fixture(params=['linux', 'macos', 'windows']) @@ -66,14 +73,6 @@ def platform(request, monkeypatch): else: monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') - marker = request.node.get_closest_marker('allow_empty') - if marker is not None and (len(marker.args) == 0 or platform_value in marker.args): - def pass_exit(val: int): - if val not in {3}: - sys.exit(val) - - monkeypatch.setattr(sys, 'exit', pass_exit) - return platform_value diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index 345cfe2a..f6c80ff7 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -40,8 +40,7 @@ def test_output_dir_argument(also_set_environment, platform, intercepted_build_a assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR -@pytest.mark.allow_empty -def test_build_selector(platform, intercepted_build_args, monkeypatch): +def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_empty): BUILD = 'some build* *-selector' SKIP = 'some skip* *-selector' @@ -223,7 +222,6 @@ def test_build_verbosity(build_verbosity, platform_specific, platform, intercept assert intercepted_build_args.args[0].build_verbosity == expected_verbosity -@pytest.mark.allow_empty @pytest.mark.parametrize('option_name', ['CIBW_BUILD', 'CIBW_SKIP']) @pytest.mark.parametrize('option_value, build_selector_patterns', [ ('*-manylinux1_*', ['*-manylinux_*']), @@ -231,7 +229,7 @@ def test_build_verbosity(build_verbosity, platform_specific, platform, intercept ('*-macosx_10_9_x86_64', ['*-macosx_x86_64']), ('cp37-macosx_10_9_x86_64', ['cp37-macosx_x86_64']), ]) -def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_name, option_value, build_selector_patterns): +def test_build_selector_migrations(intercepted_build_args, monkeypatch, option_name, option_value, build_selector_patterns, allow_empty): monkeypatch.setenv(option_name, option_value) main() diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 0f277afe..844cf811 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -67,22 +67,25 @@ def test_platform_environment(platform, intercepted_build_args, monkeypatch): assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR -@pytest.mark.allow_empty('windows') def test_archs_default(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') + monkeypatch.setattr(platform_module, 'machine', lambda: 'AMD64' if platform == 'windows' else 'x86_64') main() build_options = intercepted_build_args.args[0] if platform == 'linux': assert build_options.architectures == {Architecture.x86_64, Architecture.i686} + elif platform == 'windows': + assert build_options.architectures == {Architecture.AMD64, Architecture.x86} else: assert build_options.architectures == {Architecture.x86_64} -@pytest.mark.allow_empty('windows') @pytest.mark.parametrize('use_env_var', [False, True]) def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_var): + if platform == 'windows': + pytest.skip('Will have empty build selectors on Windows') + monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') if use_env_var: monkeypatch.setenv('CIBW_ARCHS', 'ppc64le') @@ -114,9 +117,8 @@ def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): assert build_options.architectures == {Architecture.x86_64} -@pytest.mark.allow_empty('windows') def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') + monkeypatch.setattr(platform_module, 'machine', lambda: 'AMD64' if platform == 'windows' else 'x86_64') monkeypatch.setenv('CIBW_ARCHS', 'native') main() @@ -125,7 +127,7 @@ def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): if platform == 'linux': assert build_options.architectures == {Architecture.x86_64} elif platform == 'windows': - assert build_options.architectures == {Architecture.x86_64} + assert build_options.architectures == {Architecture.AMD64} elif platform == 'macos': assert build_options.architectures == {Architecture.x86_64} From cc0e7ec0a1020a9adb41fa2c33663731f078b075 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 22 Jan 2021 08:54:19 -0500 Subject: [PATCH 22/27] fix: review from @joerick --- cibuildwheel/__main__.py | 10 +++++----- test/test_cpp_standards.py | 2 +- test/utils.py | 7 ++++--- unit_test/main_tests/main_platform_test.py | 6 ------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index bcdfef8c..538d56f1 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -257,6 +257,11 @@ def main() -> None: print_preamble(platform, build_options) + if not identifiers: + print(f'cibuildwheel: No build identifiers selected: {build_selector}', file=sys.stderr) + if not args.allow_empty: + sys.exit(3) + if not output_dir.exists(): output_dir.mkdir(parents=True) @@ -269,11 +274,6 @@ def main() -> None: else: assert_never(platform) - if not identifiers: - print(f'cibuildwheel: No build identifiers selected: {build_selector}', file=sys.stderr) - if not args.allow_empty: - sys.exit(3) - def detect_obsolete_options() -> None: # Check the old 'MANYLINUX1_*_IMAGE' options diff --git a/test/test_cpp_standards.py b/test/test_cpp_standards.py index 8ab5becd..762414bf 100644 --- a/test/test_cpp_standards.py +++ b/test/test_cpp_standards.py @@ -202,7 +202,7 @@ def test_cpp17_py27_modern_msvc_workaround(tmp_path): add_env_x64['CIBW_BUILD'] = 'cp27-win_amd64' actual_wheels += utils.cibuildwheel_run(project_dir, add_env=add_env_x64) - expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', filter_27=False) + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', exclude_27=False) if 'cp27-cp27m-win' in w or 'pp27-pypy_73-win32' in w] diff --git a/test/utils.py b/test/utils.py index 4fcdba47..8b11a0e6 100644 --- a/test/utils.py +++ b/test/utils.py @@ -77,7 +77,7 @@ def cibuildwheel_run(project_path, package_dir='.', env=None, add_env=None, outp def expected_wheels(package_name, package_version, manylinux_versions=None, macosx_deployment_target='10.9', machine_arch=None, *, - filter_27=IS_WINDOWS_RUNNING_ON_TRAVIS): + exclude_27=IS_WINDOWS_RUNNING_ON_TRAVIS): ''' Returns a list of expected wheels from a run of cibuildwheel. ''' @@ -135,8 +135,9 @@ def expected_wheels(package_name, package_version, manylinux_versions=None, for platform_tag in platform_tags: wheels.append(f'{package_name}-{package_version}-{python_abi_tag}-{platform_tag}.whl') - if filter_27: - # Python 2.7 isn't supported on Travis. + # Travis on Windows does not support using the default Python 2.7 compiler, + # so we support skipping here. + if exclude_27: wheels = [w for w in wheels if '-cp27-' not in w and '-pp2' not in w] return wheels diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 844cf811..aa004c5c 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -1,4 +1,3 @@ -import platform as platform_module import sys import pytest @@ -68,7 +67,6 @@ def test_platform_environment(platform, intercepted_build_args, monkeypatch): def test_archs_default(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'AMD64' if platform == 'windows' else 'x86_64') main() build_options = intercepted_build_args.args[0] @@ -86,7 +84,6 @@ def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_v if platform == 'windows': pytest.skip('Will have empty build selectors on Windows') - monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') if use_env_var: monkeypatch.setenv('CIBW_ARCHS', 'ppc64le') else: @@ -100,7 +97,6 @@ def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_v def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') monkeypatch.setenv('CIBW_ARCHS', 'unused') monkeypatch.setenv('CIBW_ARCHS_LINUX', 'ppc64le') monkeypatch.setenv('CIBW_ARCHS_WINDOWS', 'x86') @@ -118,7 +114,6 @@ def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'AMD64' if platform == 'windows' else 'x86_64') monkeypatch.setenv('CIBW_ARCHS', 'native') main() @@ -133,7 +128,6 @@ def test_archs_platform_native(platform, intercepted_build_args, monkeypatch): def test_archs_platform_all(platform, intercepted_build_args, monkeypatch): - monkeypatch.setattr(platform_module, 'machine', lambda: 'x86_64') monkeypatch.setenv('CIBW_ARCHS', 'all') main() From f5b84797e52181bb0387afaab725a2293a51f7d6 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 22 Jan 2021 09:23:52 -0500 Subject: [PATCH 23/27] fix: move and simplify arch check --- cibuildwheel/__main__.py | 7 +++++++ cibuildwheel/linux.py | 3 --- cibuildwheel/macos.py | 3 --- cibuildwheel/windows.py | 3 --- unit_test/main_tests/main_platform_test.py | 13 ++++++++----- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 538d56f1..010d9eea 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -20,6 +20,7 @@ from cibuildwheel.util import ( DependencyConstraints, TestSelector, Unbuffered, + allowed_architectures_check, detect_ci_provider, resources_dir, ) @@ -257,6 +258,12 @@ def main() -> None: print_preamble(platform, build_options) + try: + allowed_architectures_check(platform, build_options) + except ValueError as err: + print("cibuildwheel:", *err.args, file=sys.stderr) + sys.exit(4) + if not identifiers: print(f'cibuildwheel: No build identifiers selected: {build_selector}', file=sys.stderr) if not args.allow_empty: diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 1a104d7c..ce725b55 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -12,7 +12,6 @@ from .util import ( BuildOptions, BuildSelector, NonPlatformWheelError, - allowed_architectures_check, get_build_verbosity_extra_flags, prepare_command, read_python_configs, @@ -49,8 +48,6 @@ def get_python_configurations( def build(options: BuildOptions) -> None: - allowed_architectures_check('linux', options) - try: subprocess.check_output(['docker', '--version']) except Exception: diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 66b41b3f..cf691995 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -14,7 +14,6 @@ from .util import ( BuildOptions, BuildSelector, NonPlatformWheelError, - allowed_architectures_check, download, get_build_verbosity_extra_flags, get_pip_script, @@ -188,8 +187,6 @@ def setup_python(python_configuration: PythonConfiguration, def build(options: BuildOptions) -> None: - allowed_architectures_check("macos", options) - temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel')) built_wheel_dir = temp_dir / 'built_wheel' repaired_wheel_dir = temp_dir / 'repaired_wheel' diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 5e8c1683..e2a6d254 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -17,7 +17,6 @@ from .util import ( BuildOptions, BuildSelector, NonPlatformWheelError, - allowed_architectures_check, download, get_build_verbosity_extra_flags, get_pip_script, @@ -207,8 +206,6 @@ def pep_518_cp35_workaround(package_dir: Path, env: Dict[str, str]) -> None: def build(options: BuildOptions) -> None: - allowed_architectures_check('windows', options) - temp_dir = Path(tempfile.mkdtemp(prefix='cibuildwheel')) built_wheel_dir = temp_dir / 'built_wheel' repaired_wheel_dir = temp_dir / 'repaired_wheel' diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index aa004c5c..e0897dc8 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -81,8 +81,6 @@ def test_archs_default(platform, intercepted_build_args, monkeypatch): @pytest.mark.parametrize('use_env_var', [False, True]) def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_var): - if platform == 'windows': - pytest.skip('Will have empty build selectors on Windows') if use_env_var: monkeypatch.setenv('CIBW_ARCHS', 'ppc64le') @@ -90,10 +88,15 @@ def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_v monkeypatch.setenv('CIBW_ARCHS', 'unused') monkeypatch.setattr(sys, 'argv', sys.argv + ['--archs', 'ppc64le']) - main() - build_options = intercepted_build_args.args[0] + if platform in {'macos', 'windows'}: + with pytest.raises(SystemExit) as err: + main() + assert err.value.args == (4,) - assert build_options.architectures == {Architecture.ppc64le} + else: + main() + build_options = intercepted_build_args.args[0] + assert build_options.architectures == {Architecture.ppc64le} def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch): From ff8f8810988b5bde2b9d5ceaddbf4ded22069b5a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 22 Jan 2021 09:33:22 -0500 Subject: [PATCH 24/27] refactor: pull arch out into separate file --- cibuildwheel/__main__.py | 5 +- cibuildwheel/architecture.py | 86 ++++++++++++++++++++++ cibuildwheel/linux.py | 2 +- cibuildwheel/util.py | 86 +--------------------- cibuildwheel/windows.py | 2 +- unit_test/main_tests/main_platform_test.py | 4 +- 6 files changed, 94 insertions(+), 91 deletions(-) create mode 100644 cibuildwheel/architecture.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 010d9eea..8f65f28b 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -11,16 +11,15 @@ import cibuildwheel import cibuildwheel.linux import cibuildwheel.macos import cibuildwheel.windows +from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.environment import EnvironmentParseError, parse_environment from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never from cibuildwheel.util import ( - Architecture, BuildOptions, BuildSelector, DependencyConstraints, TestSelector, Unbuffered, - allowed_architectures_check, detect_ci_provider, resources_dir, ) @@ -259,7 +258,7 @@ def main() -> None: print_preamble(platform, build_options) try: - allowed_architectures_check(platform, build_options) + allowed_architectures_check(platform, build_options.architectures) except ValueError as err: print("cibuildwheel:", *err.args, file=sys.stderr) sys.exit(4) diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py new file mode 100644 index 00000000..f7bf4354 --- /dev/null +++ b/cibuildwheel/architecture.py @@ -0,0 +1,86 @@ +import functools +import platform as platform_module +import re +from enum import Enum +from typing import Set + +from .typing import PlatformName, assert_never + +PRETTY_NAMES = {'linux': 'Linux', 'macos': 'macOS', 'windows': 'Windows'} + + +@functools.total_ordering +class Architecture(Enum): + value: str + + # mac/linux archs + x86_64 = 'x86_64' + i686 = 'i686' + aarch64 = 'aarch64' + ppc64le = 'ppc64le' + s390x = 's390x' + + # windows archs + x86 = 'x86' + AMD64 = 'AMD64' + + # Allow this to be sorted + def __lt__(self, other: "Architecture") -> bool: + return self.value < other.value + + @staticmethod + def parse_config(config: str, platform: PlatformName) -> 'Set[Architecture]': + result = set() + for arch_str in re.split(r'[\s,]+', config): + if arch_str == 'auto': + result |= Architecture.auto_archs(platform=platform) + elif arch_str == 'native': + result.add(Architecture(platform_module.machine())) + elif arch_str == 'all': + result |= Architecture.all_archs(platform=platform) + else: + result.add(Architecture(arch_str)) + return result + + @staticmethod + def auto_archs(platform: PlatformName) -> 'Set[Architecture]': + native_architecture = Architecture(platform_module.machine()) + result = {native_architecture} + if platform == 'linux' and native_architecture == Architecture.x86_64: + # x86_64 machines can run i686 docker containers + result.add(Architecture.i686) + if platform == 'windows' and native_architecture == Architecture.AMD64: + result.add(Architecture.x86) + return result + + @staticmethod + def all_archs(platform: PlatformName) -> 'Set[Architecture]': + if platform == 'linux': + return {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x} + elif platform == 'macos': + return {Architecture.x86_64} + elif platform == 'windows': + return {Architecture.x86, Architecture.AMD64} + else: + assert_never(platform) + + +def allowed_architectures_check( + platform: PlatformName, + architectures: Set[Architecture], +) -> None: + + allowed_architectures = Architecture.all_archs(platform) + + msg = f'{PRETTY_NAMES[platform]} only supports {sorted(allowed_architectures)} at the moment.' + + if platform != 'linux': + msg += ' If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX instead.' + + if not architectures <= allowed_architectures: + msg = f'Invalid archs option {architectures}. ' + msg + raise ValueError(msg) + + if not architectures: + msg = 'Empty archs option set. ' + msg + raise ValueError(msg) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index ce725b55..13354f43 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -4,11 +4,11 @@ import textwrap from pathlib import Path, PurePath from typing import List, NamedTuple, Set +from .architecture import Architecture from .docker_container import DockerContainer from .logger import log from .typing import PathOrStr from .util import ( - Architecture, BuildOptions, BuildSelector, NonPlatformWheelError, diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 33435c16..1baf71a9 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -1,9 +1,6 @@ import fnmatch -import functools import itertools import os -import platform as platform_module -import re import ssl import sys import textwrap @@ -17,8 +14,9 @@ import bracex import certifi import toml +from .architecture import Architecture from .environment import ParsedEnvironment -from .typing import PathOrStr, PlatformName, assert_never +from .typing import PathOrStr, PlatformName if sys.version_info < (3, 9): from importlib_resources import files @@ -163,62 +161,6 @@ class DependencyConstraints: return f'{self.__class__.__name__}{self.base_file_path!r})' -@functools.total_ordering -class Architecture(Enum): - value: str - - # mac/linux archs - x86_64 = 'x86_64' - i686 = 'i686' - aarch64 = 'aarch64' - ppc64le = 'ppc64le' - s390x = 's390x' - - # windows archs - x86 = 'x86' - AMD64 = 'AMD64' - - # Allow this to be sorted - def __lt__(self, other: "Architecture") -> bool: - return self.value < other.value - - @staticmethod - def parse_config(config: str, platform: PlatformName) -> 'Set[Architecture]': - result = set() - for arch_str in re.split(r'[\s,]+', config): - if arch_str == 'auto': - result |= Architecture.auto_archs(platform=platform) - elif arch_str == 'native': - result.add(Architecture(platform_module.machine())) - elif arch_str == 'all': - result |= Architecture.all_archs(platform=platform) - else: - result.add(Architecture(arch_str)) - return result - - @staticmethod - def auto_archs(platform: PlatformName) -> 'Set[Architecture]': - native_architecture = Architecture(platform_module.machine()) - result = {native_architecture} - if platform == 'linux' and native_architecture == Architecture.x86_64: - # x86_64 machines can run i686 docker containers - result.add(Architecture.i686) - if platform == 'windows' and native_architecture == Architecture.AMD64: - result.add(Architecture.x86) - return result - - @staticmethod - def all_archs(platform: PlatformName) -> 'Set[Architecture]': - if platform == 'linux': - return {Architecture.x86_64, Architecture.i686, Architecture.aarch64, Architecture.ppc64le, Architecture.s390x} - elif platform == 'macos': - return {Architecture.x86_64} - elif platform == 'windows': - return {Architecture.x86, Architecture.AMD64} - else: - assert_never(platform) - - class BuildOptions(NamedTuple): package_dir: Path output_dir: Path @@ -286,27 +228,3 @@ def detect_ci_provider() -> Optional[CIProvider]: return CIProvider.other else: return None - - -PRETTY_NAMES = {'linux': 'Linux', 'macos': 'macOS', 'windows': 'Windows'} - - -def allowed_architectures_check( - platform: PlatformName, - options: BuildOptions, -) -> None: - - allowed_architectures = Architecture.all_archs(platform) - - msg = f'{PRETTY_NAMES[platform]} only supports {sorted(allowed_architectures)} at the moment.' - - if platform != 'linux': - msg += ' If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX instead.' - - if not options.architectures <= allowed_architectures: - msg = f'Invalid archs option {options.architectures}. ' + msg - raise ValueError(msg) - - if not options.architectures: - msg = 'Empty archs option set. ' + msg - raise ValueError(msg) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index e2a6d254..4d3487ca 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -9,11 +9,11 @@ from zipfile import ZipFile import toml +from .architecture import Architecture from .environment import ParsedEnvironment from .logger import log from .typing import PathOrStr from .util import ( - Architecture, BuildOptions, BuildSelector, NonPlatformWheelError, diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index e0897dc8..a06ffe68 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -89,9 +89,9 @@ def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_v monkeypatch.setattr(sys, 'argv', sys.argv + ['--archs', 'ppc64le']) if platform in {'macos', 'windows'}: - with pytest.raises(SystemExit) as err: + with pytest.raises(SystemExit) as exit: main() - assert err.value.args == (4,) + assert exit.value.args == (4,) else: main() From 07f972f6d3ed65c5a552223e53d72b1ab738e34a Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 22 Jan 2021 19:16:32 +0000 Subject: [PATCH 25/27] A few docs fixes --- docs/options.md | 39 ++++++++++++++++++++------------------- docs/setup.md | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/options.md b/docs/options.md index e8b72fdd..12d19031 100644 --- a/docs/options.md +++ b/docs/options.md @@ -276,20 +276,18 @@ CIBW_BEFORE_BUILD: "{package}/script/prepare_for_build.sh" !!! note If you need dependencies installed for the build, we recommend using pyproject.toml. This is an example pyproject.toml file: - ```toml - [build-system] - requires = [ - "setuptools>=42", - "wheel", - "Cython", - "numpy==1.11.3; python_version<='3.6'", - "numpy==1.14.5; python_version=='3.7'", - "numpy==1.17.3; python_version=='3.8'", - "numpy==1.19.4; python_version>='3.9'", - ] + [build-system] + requires = [ + "setuptools>=42", + "wheel", + "Cython", + "numpy==1.11.3; python_version<='3.6'", + "numpy==1.14.5; python_version=='3.7'", + "numpy==1.17.3; python_version=='3.8'", + "numpy==1.19.4; python_version>='3.9'", + ] - build-backend = "setuptools.build_meta" - ``` + build-backend = "setuptools.build_meta" This [PEP 517][]/[PEP 518][] style build allows you to completely control the build environment in cibuildwheel, [PyPA-build][], and pip, doesn't force @@ -554,7 +552,7 @@ CIBW_BUILD_VERBOSITY: 1 ```text usage: cibuildwheel [-h] [--platform {auto,linux,macos,windows}] [--archs ARCHS] [--output-dir OUTPUT_DIR] - [--print-build-identifiers] + [--print-build-identifiers] [--allow-empty] [package_dir] Build wheels for all the platforms. @@ -577,16 +575,19 @@ optional arguments: you need to run in Windows, and it will build and test for all versions of Python. Default: auto. --archs ARCHS Comma-separated list of CPU architectures to build - for. If unspecified, builds the architectures natively - supported on this machine. Set this option to build an - architecture via emulation, for example, using - binfmt_misc and qemu. Default: auto Choices: auto, - x86_64, i686, aarch64, ppc64le, s390x, x86, AMD64 + for. When set to 'auto', builds the architectures + natively supported on this machine. Set this option to + build an architecture via emulation, for example, + using binfmt_misc and QEMU. Default: auto. Choices: + auto, native, all, x86_64, i686, aarch64, ppc64le, + s390x, x86, AMD64 --output-dir OUTPUT_DIR Destination folder for the wheels. --print-build-identifiers Print the build identifiers matched by the current invocation and exit. + --allow-empty Do not report an error code if the build does not + match any wheels. ```