From 736931ad4f8e6b66e0154ec760ea86cb2e7f082f Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 30 Dec 2023 18:25:35 +0000 Subject: [PATCH 01/10] Refactor error handling to use exceptions cibuildwheel has up until now handled most errors by printing an error message to sys.stderr and calling sys.exit. Others were handled using Logger.error, depending on the context. We also had return codes, but these weren't explicitly defined anywhere. This makes that convention more explicit and codified. Now to halt the program, the correct thing to do is to throw a cibuildwheel.errors.FatalError exception - that is caught in main() and printed before exiting. The existing behaviour was kept - if an error occurs within a build step (probably something to do with the build itself), the Logger.error() method is used. Outside of a build step (e.g. a misconfiguration), the behaviour is still to print 'cibuildwheel: ' I also took the opportunity to add a debugging option `--debug-traceback` (and `CIBW_DEBUG_TRACEBACK`), which you can enable to see a full traceback on errors. (I've deactivated the flake8-errmsg lint rule, as it was throwing loads of errors and these error messages aren't generally seen in a traceback context) --- cibuildwheel/__main__.py | 91 +++++++++++++++-------- cibuildwheel/errors.py | 20 +++++ cibuildwheel/linux.py | 35 +++------ cibuildwheel/logger.py | 4 + cibuildwheel/macos.py | 16 ++-- cibuildwheel/options.py | 29 ++++---- cibuildwheel/util.py | 2 +- cibuildwheel/windows.py | 21 ++---- docs/options.md | 29 +++++++- pyproject.toml | 1 - unit_test/main_tests/main_options_test.py | 22 ++++++ unit_test/options_test.py | 4 +- 12 files changed, 176 insertions(+), 98 deletions(-) create mode 100644 cibuildwheel/errors.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index c6a2fdeaa..a9ec3d4fc 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -6,6 +6,7 @@ import sys import tarfile import textwrap +import traceback import typing from collections.abc import Iterable, Sequence, Set from pathlib import Path @@ -17,6 +18,7 @@ import cibuildwheel.macos import cibuildwheel.util import cibuildwheel.windows +from cibuildwheel import errors from cibuildwheel._compat.typing import assert_never from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.logger import log @@ -30,10 +32,36 @@ chdir, detect_ci_provider, fix_ansi_codes_for_github_actions, + strtobool, + unwrap, ) +# a global variable that decides what happens when errors are hit. +print_traceback_on_error = True + def main() -> None: + try: + main_inner() + except errors.FatalError as e: + message = e.args[0] + if log.step_active: + log.step_end_with_error(message) + else: + print(f"cibuildwheel: {message}", file=sys.stderr) + + if print_traceback_on_error: + traceback.print_exc(file=sys.stderr) + + sys.exit(e.return_code) + + +def main_inner() -> None: + """ + `main_inner` is the same as `main`, but it raises FatalError exceptions + rather than exiting directly. + """ + parser = argparse.ArgumentParser( description="Build wheels for all the platforms.", epilog=""" @@ -132,8 +160,18 @@ def main() -> None: help="Enable pre-release Python versions if available.", ) + parser.add_argument( + "--debug-traceback", + action="store_true", + default=strtobool(os.environ.get("CIBW_DEBUG_TRACEBACK", "0")), + help="Print a full traceback for all errors", + ) + args = CommandLineArguments(**vars(parser.parse_args())) + global print_traceback_on_error + print_traceback_on_error = args.debug_traceback + args.package_dir = args.package_dir.resolve() # This are always relative to the base directory, even in SDist builds @@ -177,27 +215,23 @@ def _compute_platform_only(only: str) -> PlatformName: return "macos" if "win_" in only or "win32" in only: return "windows" - print( + raise errors.ConfigurationError( f"Invalid --only='{only}', must be a build selector with a known platform", - file=sys.stderr, ) - sys.exit(2) def _compute_platform_ci() -> PlatformName: if detect_ci_provider() is None: - print( - textwrap.dedent( + raise errors.ConfigurationError( + unwrap( """ - cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server; + Unable to detect platform. cibuildwheel should run on your CI server; Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI are 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, ) - sys.exit(2) if sys.platform.startswith("linux"): return "linux" elif sys.platform == "darwin": @@ -205,33 +239,28 @@ def _compute_platform_ci() -> PlatformName: elif sys.platform == "win32": return "windows" else: - print( - 'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run ' + raise errors.ConfigurationError( + '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, ) - sys.exit(2) def _compute_platform(args: CommandLineArguments) -> PlatformName: platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto") if args.only and args.platform is not None: - print( + raise errors.ConfigurationError( "--platform cannot be specified with --only, it is computed from --only", - file=sys.stderr, ) - sys.exit(2) if args.only and args.archs is not None: - print( + raise errors.ConfigurationError( "--arch cannot be specified with --only, it is computed from --only", - file=sys.stderr, ) - sys.exit(2) if platform_option_value not in PLATFORMS | {"auto"}: - print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr) - sys.exit(2) + raise errors.ConfigurationError( + f"Unsupported platform: {platform_option_value}", + ) if args.only: return _compute_platform_only(args.only) @@ -273,9 +302,7 @@ def build_in_directory(args: CommandLineArguments) -> None: if not any(package_dir.joinpath(name).exists() for name in package_files): names = ", ".join(sorted(package_files, reverse=True)) - msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package" - print(msg, file=sys.stderr) - sys.exit(2) + raise errors.ConfigurationError(f"Could not find any of {{{names}}} at root of package") platform_module = get_platform_module(platform) identifiers = get_build_identifiers( @@ -304,16 +331,14 @@ def build_in_directory(args: CommandLineArguments) -> None: options.check_for_invalid_configuration(identifiers) allowed_architectures_check(platform, options.globals.architectures) except ValueError as err: - print("cibuildwheel:", *err.args, file=sys.stderr) - sys.exit(4) + raise errors.DeprecationError(*err.args) from err if not identifiers: - print( - f"cibuildwheel: No build identifiers selected: {options.globals.build_selector}", - file=sys.stderr, - ) - if not args.allow_empty: - sys.exit(3) + message = f"No build identifiers selected: {options.globals.build_selector}" + if args.allow_empty: + print(f"cibuildwheel: {message}", file=sys.stderr) + else: + raise errors.NothingToDoError(message) output_dir = options.globals.output_dir @@ -368,7 +393,9 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str]) def get_build_identifiers( - platform_module: PlatformModule, build_selector: BuildSelector, architectures: Set[Architecture] + platform_module: PlatformModule, + build_selector: BuildSelector, + architectures: Set[Architecture], ) -> list[str]: python_configurations = platform_module.get_python_configurations(build_selector, architectures) return [config.identifier for config in python_configurations] diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py new file mode 100644 index 000000000..7ac2c9c49 --- /dev/null +++ b/cibuildwheel/errors.py @@ -0,0 +1,20 @@ +class FatalError(SystemExit): + """ + Raising an error of this type will cause the message to be printed to + stderr and the process to be terminated. Within cibuildwheel, raising this + exception produces a better error message, and optional traceback. + """ + + return_code: int = 1 + + +class ConfigurationError(FatalError): + return_code = 2 + + +class NothingToDoError(FatalError): + return_code = 3 + + +class DeprecationError(FatalError): + return_code = 4 diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 7b1a85af7..8a7eb7db9 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -8,6 +8,7 @@ from pathlib import Path, PurePath, PurePosixPath from typing import OrderedDict, Tuple +from . import errors from ._compat.typing import assert_never from .architecture import Architecture from .logger import log @@ -139,11 +140,7 @@ def check_all_python_exist( exist = False if not exist: message = "\n".join(messages) - print( - f"cibuildwheel:\n{message}", - file=sys.stderr, - ) - sys.exit(1) + raise errors.FatalError(message) def build_in_container( @@ -215,19 +212,15 @@ def build_in_container( # check config python is still on PATH which_python = container.call(["which", "python"], env=env, capture_output=True).strip() if PurePosixPath(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, + raise errors.FatalError( + "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.", ) - sys.exit(1) which_pip = container.call(["which", "pip"], env=env, capture_output=True).strip() if PurePosixPath(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, + raise errors.FatalError( + "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.", ) - sys.exit(1) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) if compatible_wheel: @@ -395,8 +388,8 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 check=True, stdout=subprocess.DEVNULL, ) - except subprocess.CalledProcessError: - print( + except subprocess.CalledProcessError as e: + raise errors.ConfigurationError( unwrap( f""" cibuildwheel: {options.globals.container_engine} not found. An @@ -407,9 +400,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 If you're building on Cirrus CI, use `docker_builder` task. """ ), - file=sys.stderr, - ) - sys.exit(2) + ) from e python_configurations = get_python_configurations( options.globals.build_selector, options.globals.architectures @@ -446,11 +437,10 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 ) except subprocess.CalledProcessError as error: - log.step_end_with_error( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}" - ) troubleshoot(options, error) - sys.exit(1) + raise errors.FatalError( + f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + ) from error def _matches_prepared_command(error_cmd: Sequence[str], command_template: str) -> bool: @@ -469,7 +459,6 @@ def troubleshoot(options: Options, error: Exception) -> None: ) # TODO allow matching of overrides too? ): # the wheel build step or the repair step failed - print("Checking for common errors...") so_files = list(options.globals.package_dir.glob("**/*.so")) if so_files: diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 0aa5263f5..ce7344f32 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -151,6 +151,10 @@ def error(self, error: BaseException | str) -> None: c = self.colors print(f"{c.bright_red}Error{c.end}: {error}\n", file=sys.stderr) + @property + def step_active(self) -> bool: + return self.step_start_time is not None + def _start_fold_group(self, name: str) -> None: self._end_fold_group() self.active_fold_group_name = name diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index bf2290f36..d79e1903d 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -16,6 +16,7 @@ from filelock import FileLock +from . import errors from ._compat.typing import assert_never from .architecture import Architecture from .environment import ParsedEnvironment @@ -218,22 +219,18 @@ def setup_python( call("pip", "--version", env=env) which_pip = call("which", "pip", env=env, capture_stdout=True).strip() if which_pip != str(venv_bin_path / "pip"): - print( + raise errors.FatalError( "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, ) - sys.exit(1) # check what Python version we're on call("which", "python", env=env) call("python", "--version", env=env) which_python = call("which", "python", env=env, capture_stdout=True).strip() if which_python != str(venv_bin_path / "python"): - print( + raise errors.FatalError( "cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", - file=sys.stderr, ) - sys.exit(1) config_is_arm64 = python_configuration.identifier.endswith("arm64") config_is_universal2 = python_configuration.identifier.endswith("universal2") @@ -624,7 +621,6 @@ def build(options: Options, tmp_path: Path) -> None: log.build_end() except subprocess.CalledProcessError as error: - log.step_end_with_error( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}" - ) - sys.exit(1) + raise errors.FatalError( + f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + ) from error diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index f5664cbf1..734ab29f7 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -9,13 +9,13 @@ import shlex import sys import textwrap -import traceback from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Set from pathlib import Path from typing import Any, Dict, List, Literal, TypedDict, Union from packaging.specifiers import SpecifierSet +from . import errors from ._compat import tomllib from ._compat.typing import NotRequired from .architecture import Architecture @@ -51,6 +51,7 @@ class CommandLineArguments: print_build_identifiers: bool allow_empty: bool prerelease_pythons: bool + debug_traceback: bool @staticmethod def defaults() -> CommandLineArguments: @@ -64,6 +65,7 @@ def defaults() -> CommandLineArguments: package_dir=Path("."), prerelease_pythons=False, print_build_identifiers=False, + debug_traceback=False, ) @@ -467,9 +469,7 @@ def globals(self) -> GlobalOptions: try: container_engine = OCIContainerEngineConfig.from_config_string(container_engine_str) except ValueError as e: - msg = f"cibuildwheel: Failed to parse container config. {e}" - print(msg, file=sys.stderr) - sys.exit(2) + raise errors.ConfigurationError(f"Failed to parse container config. {e}") from e return GlobalOptions( package_dir=package_dir, @@ -517,18 +517,14 @@ def build_options(self, identifier: str | None) -> BuildOptions: try: build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str) except ValueError as e: - print(f"cibuildwheel: {e}", file=sys.stderr) - sys.exit(2) + raise errors.ConfigurationError(f"Failed to parse build frontend. {e}") from e try: environment = parse_environment(environment_config) - except (EnvironmentParseError, ValueError): - print( - f"cibuildwheel: Malformed environment option {environment_config!r}", - file=sys.stderr, - ) - traceback.print_exc(None, sys.stderr) - sys.exit(2) + except (EnvironmentParseError, ValueError) as e: + raise errors.ConfigurationError( + f"Malformed environment option {environment_config!r}", + ) from e # Pass through environment variables if self.platform == "linux": @@ -767,6 +763,7 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]: def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None: if "p2" in selector or "p35" in selector: msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}" - print(msg, file=sys.stderr) - if error: - sys.exit(4) + if not error: + print(msg, file=sys.stderr) + else: + raise errors.DeprecationError(msg) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index ae7afbb44..5ad04541e 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -379,7 +379,7 @@ def from_config_string(config_string: str) -> BuildFrontendConfig: config_dict = parse_key_value_string(config_string, ["name"], ["args"]) name = " ".join(config_dict["name"]) if name not in {"pip", "build"}: - msg = f"Unrecognised build frontend {name}, only 'pip' and 'build' are supported" + msg = f"Unrecognised build frontend '{name}', only 'pip' and 'build' are supported" raise ValueError(msg) name = typing.cast(BuildFrontendName, name) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 08bc77331..adfc17ee5 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -4,7 +4,6 @@ import platform as platform_module import shutil import subprocess -import sys import textwrap from collections.abc import MutableMapping, Sequence, Set from contextlib import suppress @@ -16,6 +15,7 @@ from filelock import FileLock from packaging.version import Version +from . import errors from ._compat.typing import assert_never from .architecture import Architecture from .environment import ParsedEnvironment @@ -296,21 +296,17 @@ def setup_python( call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env) where_python = call("where", "python", env=env, capture_stdout=True).splitlines()[0].strip() if where_python != str(venv_path / "Scripts" / "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, + raise errors.FatalError( + "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.", ) - sys.exit(1) # check what pip version we're on assert (venv_path / "Scripts" / "pip.exe").exists() where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip() if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"): - 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, + raise errors.FatalError( + "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.", ) - sys.exit(1) call("pip", "--version", env=env) @@ -578,7 +574,6 @@ def build(options: Options, tmp_path: Path) -> None: log.build_end() except subprocess.CalledProcessError as error: - log.step_end_with_error( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}" - ) - sys.exit(1) + raise errors.FatalError( + f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + ) from error diff --git a/docs/options.md b/docs/options.md index f7f058f12..a3949e337 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1442,6 +1442,20 @@ Default: Off (0). export CIBW_DEBUG_KEEP_CONTAINER=TRUE ``` +### `CIBW_DEBUG_TRACEBACK` +> Print full traceback when errors occur. + +Print a full traceback for the cibuildwheel process when errors occur. This +option is provided for debugging cibuildwheel. + +This option can also be set using the [command-line option](#command-line) `--debug-traceback`. + +#### Examples + +```shell +export CIBW_DEBUG_TRACEBACK=TRUE +``` + ### `CIBW_BUILD_VERBOSITY` {: #build-verbosity} > Increase/decrease the output of pip wheel @@ -1468,12 +1482,25 @@ Platform-specific environment variables are also available:
``` -## Command line options {: #command-line} +## Command line {: #command-line} + +### Options ```text « subprocess_run("cibuildwheel", "--help") » ``` +### Return codes + +cibuildwheel exits 0 on success, or >0 if an error occurs. + +Specific error codes are defined: + +- 2 means a configuration error +- 3 means no builds are selected (and --allow-empty wasn't passed) +- 4 means you specified an option that has been deprecated. + + ## Placeholders Some options support placeholders, like `{project}`, `{package}` or `{wheel}`, that are substituted by cibuildwheel before they are used. If, for some reason, you need to write the literal name of a placeholder, e.g. literally `{project}` in a command that would ordinarily substitute `{project}`, prefix it with a hash character - `#{project}`. This is only necessary in commands where the specific string between the curly brackets would be substituted - otherwise, strings not modified. diff --git a/pyproject.toml b/pyproject.toml index 64726c492..c6005e38a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,6 @@ extend-select = [ "I", # isort "ARG", # flake8-unused-arguments "C4", # flake8-comprehensions - "EM", # flake8-errmsg "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat "G", # flake8-logging-format diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index 5b85da73b..acd37c1bc 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -344,6 +344,28 @@ def test_before_all(before_all, platform_specific, platform, intercepted_build_a assert build_options.before_all == (before_all or "") +@pytest.mark.parametrize("method", ["unset", "command_line", "env_var"]) +def test_debug_traceback(monkeypatch, method, capfd): + if method == "command_line": + monkeypatch.setattr(sys, "argv", [*sys.argv, "--debug-traceback"]) + elif method == "env_var": + monkeypatch.setenv("CIBW_DEBUG_TRACEBACK", "TRUE") + + # set an option that produces a configuration error + monkeypatch.setenv("CIBW_BUILD_FRONTEND", "invalid_value") + + with pytest.raises(SystemExit) as exit: + main() + assert exit.value.code == 2 + + _, err = capfd.readouterr() + + if method == "unset": + assert "Traceback (most recent call last)" not in err + else: + assert "Traceback (most recent call last)" in err + + def test_defaults(platform, intercepted_build_args): main() diff --git a/unit_test/options_test.py b/unit_test/options_test.py index 3a823d6c4..0f40792f3 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -7,6 +7,7 @@ import pytest +from cibuildwheel import errors from cibuildwheel.__main__ import get_build_identifiers, get_platform_module from cibuildwheel.bashlex_eval import local_environment_executor from cibuildwheel.environment import parse_environment @@ -127,7 +128,8 @@ def test_passthrough_evil(tmp_path, monkeypatch, env_var_value): xfail_env_parse = pytest.mark.xfail( - raises=SystemExit, reason="until we can figure out the right way to quote these values" + raises=errors.ConfigurationError, + reason="until we can figure out the right way to quote these values", ) From 8f0f4ba234ae5b6b646fb03d8dc5073f34102cd1 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 6 Jan 2024 14:51:57 +0000 Subject: [PATCH 02/10] add noqa rule --- cibuildwheel/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index a9ec3d4fc..2a4153f75 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -169,7 +169,7 @@ def main_inner() -> None: args = CommandLineArguments(**vars(parser.parse_args())) - global print_traceback_on_error + global print_traceback_on_error # noqa: PLW0603 print_traceback_on_error = args.debug_traceback args.package_dir = args.package_dir.resolve() From 403a1326847028eebf615a6834e336c1c5293f18 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 19 Jan 2024 17:30:10 +0000 Subject: [PATCH 03/10] Apply suggestions from code review Co-authored-by: Henry Schreiner --- cibuildwheel/options.py | 6 +++--- cibuildwheel/util.py | 2 +- cibuildwheel/windows.py | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 734ab29f7..14a02994c 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -763,7 +763,7 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]: def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None: if "p2" in selector or "p35" in selector: msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}" - if not error: - print(msg, file=sys.stderr) - else: + if error: raise errors.DeprecationError(msg) + + print(msg, file=sys.stderr) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 5ad04541e..86f529b7a 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -379,7 +379,7 @@ def from_config_string(config_string: str) -> BuildFrontendConfig: config_dict = parse_key_value_string(config_string, ["name"], ["args"]) name = " ".join(config_dict["name"]) if name not in {"pip", "build"}: - msg = f"Unrecognised build frontend '{name}', only 'pip' and 'build' are supported" + msg = f"Unrecognised build frontend {name!r}, only 'pip' and 'build' are supported" raise ValueError(msg) name = typing.cast(BuildFrontendName, name) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index adfc17ee5..fa238be67 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -574,6 +574,5 @@ def build(options: Options, tmp_path: Path) -> None: log.build_end() except subprocess.CalledProcessError as error: - raise errors.FatalError( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" - ) from error + msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + raise errors.FatalError(msg) from error From 26316266c4e23bd6a6890fa4d4802e007a830de9 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 19 Jan 2024 17:47:55 +0000 Subject: [PATCH 04/10] Return to flake8-errmsg conformance --- cibuildwheel/__main__.py | 49 ++++++++++++++++++++-------------------- cibuildwheel/extra.py | 2 +- cibuildwheel/linux.py | 15 +++++------- cibuildwheel/macos.py | 15 +++++------- cibuildwheel/options.py | 11 +++++---- cibuildwheel/windows.py | 10 ++++---- pyproject.toml | 1 + 7 files changed, 48 insertions(+), 55 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 2a4153f75..20ea56ca4 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -215,23 +215,21 @@ def _compute_platform_only(only: str) -> PlatformName: return "macos" if "win_" in only or "win32" in only: return "windows" - raise errors.ConfigurationError( - f"Invalid --only='{only}', must be a build selector with a known platform", - ) + msg = f"Invalid --only='{only}', must be a build selector with a known platform" + raise errors.ConfigurationError(msg) def _compute_platform_ci() -> PlatformName: if detect_ci_provider() is None: - raise errors.ConfigurationError( - unwrap( - """ - Unable to detect platform. cibuildwheel should run on your CI server; - Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI - are supported. You can run on your development machine or other CI providers - using the --platform argument. Check --help output for more information. - """ - ), + msg = unwrap( + """ + Unable to detect platform. cibuildwheel should run on your CI server; + Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI + are supported. You can run on your development machine or other CI providers + using the --platform argument. Check --help output for more information. + """ ) + raise errors.ConfigurationError(msg) if sys.platform.startswith("linux"): return "linux" elif sys.platform == "darwin": @@ -239,28 +237,28 @@ def _compute_platform_ci() -> PlatformName: elif sys.platform == "win32": return "windows" else: - raise errors.ConfigurationError( - '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.", + msg = unwrap( + """ + 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. + """ ) + raise errors.ConfigurationError(msg) def _compute_platform(args: CommandLineArguments) -> PlatformName: platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto") if args.only and args.platform is not None: - raise errors.ConfigurationError( - "--platform cannot be specified with --only, it is computed from --only", - ) + msg = "--platform cannot be specified with --only, it is computed from --only" + raise errors.ConfigurationError(msg) if args.only and args.archs is not None: - raise errors.ConfigurationError( - "--arch cannot be specified with --only, it is computed from --only", - ) + msg = "--arch cannot be specified with --only, it is computed from --only" + raise errors.ConfigurationError(msg) if platform_option_value not in PLATFORMS | {"auto"}: - raise errors.ConfigurationError( - f"Unsupported platform: {platform_option_value}", - ) + msg = f"Unsupported platform: {platform_option_value}" + raise errors.ConfigurationError(msg) if args.only: return _compute_platform_only(args.only) @@ -302,7 +300,8 @@ def build_in_directory(args: CommandLineArguments) -> None: if not any(package_dir.joinpath(name).exists() for name in package_files): names = ", ".join(sorted(package_files, reverse=True)) - raise errors.ConfigurationError(f"Could not find any of {{{names}}} at root of package") + msg = f"Could not find any of {{{names}}} at root of package" + raise errors.ConfigurationError(msg) platform_module = get_platform_module(platform) identifiers = get_build_identifiers( diff --git a/cibuildwheel/extra.py b/cibuildwheel/extra.py index a87ddfbd0..9e8ae8ecf 100644 --- a/cibuildwheel/extra.py +++ b/cibuildwheel/extra.py @@ -17,7 +17,7 @@ def __str__(self) -> str: def dump_python_configurations( - inp: Mapping[str, Mapping[str, Sequence[Mapping[str, Printable]]]] + inp: Mapping[str, Mapping[str, Sequence[Mapping[str, Printable]]]], ) -> str: output = StringIO() for header, values in inp.items(): diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 8a7eb7db9..44f6da3f5 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -212,15 +212,13 @@ def build_in_container( # check config python is still on PATH which_python = container.call(["which", "python"], env=env, capture_output=True).strip() if PurePosixPath(which_python) != python_bin / "python": - raise errors.FatalError( - "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.", - ) + msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." + raise errors.FatalError(msg) which_pip = container.call(["which", "pip"], env=env, capture_output=True).strip() if PurePosixPath(which_pip) != python_bin / "pip": - raise errors.FatalError( - "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.", - ) + msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it." + raise errors.FatalError(msg) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) if compatible_wheel: @@ -438,9 +436,8 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 except subprocess.CalledProcessError as error: troubleshoot(options, error) - raise errors.FatalError( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" - ) from error + msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + raise errors.FatalError(msg) from error def _matches_prepared_command(error_cmd: Sequence[str], command_template: str) -> bool: diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index d79e1903d..5e699db42 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -219,18 +219,16 @@ def setup_python( call("pip", "--version", env=env) which_pip = call("which", "pip", env=env, capture_stdout=True).strip() if which_pip != str(venv_bin_path / "pip"): - raise errors.FatalError( - "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.", - ) + msg = "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." + raise errors.FatalError(msg) # check what Python version we're on call("which", "python", env=env) call("python", "--version", env=env) which_python = call("which", "python", env=env, capture_stdout=True).strip() if which_python != str(venv_bin_path / "python"): - raise errors.FatalError( - "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.", - ) + msg = "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." + raise errors.FatalError(msg) config_is_arm64 = python_configuration.identifier.endswith("arm64") config_is_universal2 = python_configuration.identifier.endswith("universal2") @@ -621,6 +619,5 @@ def build(options: Options, tmp_path: Path) -> None: log.build_end() except subprocess.CalledProcessError as error: - raise errors.FatalError( - f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" - ) from error + msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}" + raise errors.FatalError(msg) from error diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 14a02994c..14a488de7 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -469,7 +469,8 @@ def globals(self) -> GlobalOptions: try: container_engine = OCIContainerEngineConfig.from_config_string(container_engine_str) except ValueError as e: - raise errors.ConfigurationError(f"Failed to parse container config. {e}") from e + msg = f"Failed to parse container config. {e}" + raise errors.ConfigurationError(msg) from e return GlobalOptions( package_dir=package_dir, @@ -517,14 +518,14 @@ def build_options(self, identifier: str | None) -> BuildOptions: try: build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str) except ValueError as e: - raise errors.ConfigurationError(f"Failed to parse build frontend. {e}") from e + msg = f"Failed to parse build frontend. {e}" + raise errors.ConfigurationError(msg) from e try: environment = parse_environment(environment_config) except (EnvironmentParseError, ValueError) as e: - raise errors.ConfigurationError( - f"Malformed environment option {environment_config!r}", - ) from e + msg = f"Malformed environment option {environment_config!r}" + raise errors.ConfigurationError(msg) from e # Pass through environment variables if self.platform == "linux": diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index fa238be67..eddc866e0 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -296,17 +296,15 @@ def setup_python( call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env) where_python = call("where", "python", env=env, capture_stdout=True).splitlines()[0].strip() if where_python != str(venv_path / "Scripts" / "python.exe"): - raise errors.FatalError( - "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.", - ) + msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." + raise errors.FatalError(msg) # check what pip version we're on assert (venv_path / "Scripts" / "pip.exe").exists() where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip() if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"): - raise errors.FatalError( - "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.", - ) + msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it." + raise errors.FatalError(msg) call("pip", "--version", env=env) diff --git a/pyproject.toml b/pyproject.toml index c6005e38a..64726c492 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,6 +120,7 @@ extend-select = [ "I", # isort "ARG", # flake8-unused-arguments "C4", # flake8-comprehensions + "EM", # flake8-errmsg "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat "G", # flake8-logging-format From 46e437d87c885e1793d34de2e306327b3090814d Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 26 Jan 2024 19:05:47 +0000 Subject: [PATCH 05/10] Code review suggestions --- cibuildwheel/errors.py | 7 +++++++ cibuildwheel/linux.py | 25 ++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py index 7ac2c9c49..54253d673 100644 --- a/cibuildwheel/errors.py +++ b/cibuildwheel/errors.py @@ -1,3 +1,10 @@ +""" +Errors that can cause the build to fail. Each subclass of FatalError has +a different return code, by defining them all here, we can ensure that they're +semantically clear and unique. +""" + + class FatalError(SystemExit): """ Raising an error of this type will cause the message to be printed to diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 44f6da3f5..11bd87986 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -386,19 +386,18 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 check=True, stdout=subprocess.DEVNULL, ) - except subprocess.CalledProcessError as e: - raise errors.ConfigurationError( - unwrap( - f""" - cibuildwheel: {options.globals.container_engine} not found. An - OCI exe like Docker or Podman is required to run Linux builds. - 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. - If you're building on Cirrus CI, use `docker_builder` task. - """ - ), - ) from e + except subprocess.CalledProcessError as error: + msg = unwrap( + f""" + cibuildwheel: {options.globals.container_engine} not found. An OCI + exe like Docker or Podman is required to run Linux builds. 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. If you're + building on Cirrus CI, use `docker_builder` task. + """ + ) + raise errors.ConfigurationError(msg) from error python_configurations = get_python_configurations( options.globals.build_selector, options.globals.architectures From aab01953d3082e47089856d915abb0115a0a2feb Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 2 Mar 2024 18:35:54 +0000 Subject: [PATCH 06/10] Subclass Exception rather than SystemExit --- cibuildwheel/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py index 54253d673..4f66b8459 100644 --- a/cibuildwheel/errors.py +++ b/cibuildwheel/errors.py @@ -5,7 +5,7 @@ """ -class FatalError(SystemExit): +class FatalError(Exception): """ Raising an error of this type will cause the message to be printed to stderr and the process to be terminated. Within cibuildwheel, raising this From 0295e916ed2dc20676d1da230bf8027366c625ea Mon Sep 17 00:00:00 2001 From: mayeut Date: Sun, 9 Jun 2024 17:10:04 +0200 Subject: [PATCH 07/10] apply error handling to new code and fix merge issues --- cibuildwheel/linux.py | 2 +- cibuildwheel/macos.py | 8 +++----- cibuildwheel/options.py | 3 +-- cibuildwheel/pyodide.py | 28 ++++++++++------------------ 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 8f6910fdb..54ab69f6a 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -418,7 +418,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 check=True, stdout=subprocess.DEVNULL, ) - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as error: msg = unwrap( f""" cibuildwheel: {build_step.container_engine.name} not found. An diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 24fb732df..8014b3e45 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -6,7 +6,6 @@ import re import shutil import subprocess -import sys import typing from collections.abc import Sequence, Set from dataclasses import dataclass @@ -128,14 +127,13 @@ def install_cpython(tmp: Path, version: str, url: str, free_threading: bool) -> if detect_ci_provider() is None: # if running locally, we don't want to install CPython with sudo # let the user know & provide a link to the installer - print( + msg = ( f"Error: CPython {version} is not installed.\n" "cibuildwheel will not perform system-wide installs when running outside of CI.\n" f"To build locally, install CPython {version} on this machine, or, disable this version of Python using CIBW_SKIP=cp{version.replace('.', '')}-macosx_*\n" - f"\nDownload link: {url}", - file=sys.stderr, + f"\nDownload link: {url}" ) - raise SystemExit(1) + raise errors.FatalError(msg) pkg_path = tmp / "Python.pkg" # download the pkg download(url, pkg_path) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 593fd3b4a..db4faf5cb 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -10,8 +10,7 @@ import shlex import sys import textwrap -import traceback -from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Set +from collections.abc import Callable, Generator, Iterable, Iterator, Set from pathlib import Path from typing import Any, Literal, Mapping, Sequence, TypedDict, Union # noqa: TID251 diff --git a/cibuildwheel/pyodide.py b/cibuildwheel/pyodide.py index e9e75a5fe..2262522ad 100644 --- a/cibuildwheel/pyodide.py +++ b/cibuildwheel/pyodide.py @@ -2,13 +2,13 @@ import os import shutil -import sys from collections.abc import Sequence, Set from dataclasses import dataclass from pathlib import Path from filelock import FileLock +from . import errors from .architecture import Architecture from .environment import ParsedEnvironment from .logger import log @@ -96,11 +96,8 @@ def get_base_python(identifier: str) -> Path: python_name = f"python{major_minor}" which_python = shutil.which(python_name) if which_python is None: - print( - f"Error: CPython {major_minor} is not installed.", - file=sys.stderr, - ) - raise SystemExit(1) + msg = f"CPython {major_minor} is not installed." + raise errors.FatalError(msg) return Path(which_python) @@ -141,22 +138,16 @@ def setup_python( call("pip", "--version", env=env) which_pip = call("which", "pip", env=env, capture_stdout=True).strip() if which_pip != str(venv_bin_path / "pip"): - print( - "cibuildwheel: pip available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it.", - file=sys.stderr, - ) - sys.exit(1) + msg = "pip available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it." + raise errors.FatalError(msg) # check what Python version we're on call("which", "python", env=env) call("python", "--version", env=env) which_python = call("which", "python", env=env, capture_stdout=True).strip() if which_python != str(venv_bin_path / "python"): - print( - "cibuildwheel: python available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", - file=sys.stderr, - ) - sys.exit(1) + msg = "python available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." + raise errors.FatalError(msg) log.step("Installing build tools...") call( @@ -219,8 +210,9 @@ def build(options: Options, tmp_path: Path) -> None: build_frontend = build_options.build_frontend or BuildFrontendConfig("build") if build_frontend.name == "pip": - print("The pyodide platform doesn't support pip frontend", file=sys.stderr) - sys.exit(1) + msg = "The pyodide platform doesn't support pip frontend" + raise errors.FatalError(msg) + log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier From e65d6765d5ce3f1c27a249b894702e5c117eb89e Mon Sep 17 00:00:00 2001 From: mayeut Date: Sun, 9 Jun 2024 17:30:24 +0200 Subject: [PATCH 08/10] Apply review suggestion --- cibuildwheel/__main__.py | 17 ++++++++++------- cibuildwheel/options.py | 4 +--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index acc532426..a409d725b 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import dataclasses import os import shutil import sys @@ -36,13 +37,16 @@ strtobool, ) -# a global variable that decides what happens when errors are hit. -print_traceback_on_error = True + +@dataclasses.dataclass +class GlobalOptions: + print_traceback_on_error: bool = True # decides what happens when errors are hit. def main() -> None: + global_options = GlobalOptions() try: - main_inner() + main_inner(global_options) except errors.FatalError as e: message = e.args[0] if log.step_active: @@ -50,13 +54,13 @@ def main() -> None: else: print(f"cibuildwheel: {message}", file=sys.stderr) - if print_traceback_on_error: + if global_options.print_traceback_on_error: traceback.print_exc(file=sys.stderr) sys.exit(e.return_code) -def main_inner() -> None: +def main_inner(global_options: GlobalOptions) -> None: """ `main_inner` is the same as `main`, but it raises FatalError exceptions rather than exiting directly. @@ -168,8 +172,7 @@ def main_inner() -> None: args = CommandLineArguments(**vars(parser.parse_args())) - global print_traceback_on_error # noqa: PLW0603 - print_traceback_on_error = args.debug_traceback + global_options.print_traceback_on_error = args.debug_traceback args.package_dir = args.package_dir.resolve() diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index db4faf5cb..a2b1b7a8b 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -8,7 +8,6 @@ import enum import functools import shlex -import sys import textwrap from collections.abc import Callable, Generator, Iterable, Iterator, Set from pathlib import Path @@ -861,5 +860,4 @@ def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> No msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}" if error: raise errors.DeprecationError(msg) - - print(msg, file=sys.stderr) + log.warning(msg) From 750d33f6232f7570cb4f8f5589ea83dbf426ea01 Mon Sep 17 00:00:00 2001 From: mayeut Date: Sun, 9 Jun 2024 23:44:58 +0200 Subject: [PATCH 09/10] fix: merge issue --- cibuildwheel/macos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 48581a560..c07574df2 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -6,6 +6,7 @@ import re import shutil import subprocess +import sys import typing from collections.abc import Sequence, Set from dataclasses import dataclass From cf10c9713a6469e7baa78fb81a798e2c78c794d6 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 10 Jun 2024 02:07:37 -0400 Subject: [PATCH 10/10] Update cibuildwheel/errors.py --- cibuildwheel/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py index 4f66b8459..717260e54 100644 --- a/cibuildwheel/errors.py +++ b/cibuildwheel/errors.py @@ -5,7 +5,7 @@ """ -class FatalError(Exception): +class FatalError(BaseException): """ Raising an error of this type will cause the message to be printed to stderr and the process to be terminated. Within cibuildwheel, raising this