diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 59a1ca7e2..29ddb9904 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -95,43 +95,46 @@ def _check_engine_version(engine: OCIContainerEngineConfig) -> None: try: version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True) version_info = json.loads(version_string.strip()) - if engine.name == "docker": - client_api_version = FlexibleVersion(version_info["Client"]["ApiVersion"]) - server_api_version = FlexibleVersion(version_info["Server"]["ApiVersion"]) - # --platform support was introduced in 1.32 as experimental, 1.41 removed the experimental flag - version = min(client_api_version, server_api_version) - minimum_version = FlexibleVersion("1.41") - minimum_version_str = "20.10.0" # docker version - error_msg = textwrap.dedent( - f""" - Build failed because {engine.name} is too old. - - cibuildwheel requires {engine.name}>={minimum_version_str} running API version {minimum_version}. - The API version found by cibuildwheel is {version}. - """ - ) - elif engine.name == "podman": - # podman uses the same version string for "Version" & "ApiVersion" - client_version = FlexibleVersion(version_info["Client"]["Version"]) - if "Server" in version_info: - server_version = FlexibleVersion(version_info["Server"]["Version"]) - else: - server_version = client_version - # --platform support was introduced in v3 - version = min(client_version, server_version) - minimum_version = FlexibleVersion("3") - error_msg = textwrap.dedent( - f""" - Build failed because {engine.name} is too old. - - cibuildwheel requires {engine.name}>={minimum_version}. - The version found by cibuildwheel is {version}. - """ - ) - else: - assert_never(engine.name) + match engine.name: + case "docker": + client_api_version = FlexibleVersion(version_info["Client"]["ApiVersion"]) + server_api_version = FlexibleVersion(version_info["Server"]["ApiVersion"]) + # --platform support was introduced in 1.32 as experimental, 1.41 removed the experimental flag + version = min(client_api_version, server_api_version) + minimum_version = FlexibleVersion("1.41") + minimum_version_str = "20.10.0" # docker version + error_msg = textwrap.dedent( + f""" + Build failed because {engine.name} is too old. + + cibuildwheel requires {engine.name}>={minimum_version_str} running API version {minimum_version}. + The API version found by cibuildwheel is {version}. + """ + ) + case "podman": + # podman uses the same version string for "Version" & "ApiVersion" + client_version = FlexibleVersion(version_info["Client"]["Version"]) + if "Server" in version_info: + server_version = FlexibleVersion(version_info["Server"]["Version"]) + else: + server_version = client_version + # --platform support was introduced in v3 + version = min(client_version, server_version) + minimum_version = FlexibleVersion("3") + error_msg = textwrap.dedent( + f""" + Build failed because {engine.name} is too old. + + cibuildwheel requires {engine.name}>={minimum_version}. + The version found by cibuildwheel is {version}. + """ + ) + case _: + assert_never(engine.name) + if version < minimum_version: raise OCIEngineTooOldError(error_msg) from None + except (subprocess.CalledProcessError, KeyError, ValueError) as e: msg = f"Build failed because {engine.name} is too old or is not working properly." raise OCIEngineTooOldError(msg) from e diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 7c98f1e21..e88998b29 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -343,40 +343,46 @@ def _apply_inherit_rule( msg = f"Don't know how to merge {before!r} and {after!r} with {rule}" raise OptionsReaderError(msg) - if rule == InheritRule.APPEND: - return option_format.merge_values(before, after) - if rule == InheritRule.PREPEND: - return option_format.merge_values(after, before) - - assert_never(rule) + match rule: + case InheritRule.APPEND: + return option_format.merge_values(before, after) + case InheritRule.PREPEND: + return option_format.merge_values(after, before) + case _: + assert_never(rule) def _stringify_setting( setting: SettingValue, option_format: OptionFormat | None, ) -> str: - if isinstance(setting, Mapping): - try: - if option_format is None: - raise OptionFormat.NotSupported - return option_format.format_table(setting) - except OptionFormat.NotSupported: - msg = f"Error converting {setting!r} to a string: this setting doesn't accept a table" - raise OptionsReaderError(msg) from None - - if not isinstance(setting, str) and isinstance(setting, Sequence): - try: - if option_format is None: - raise OptionFormat.NotSupported - return option_format.format_list(setting) - except OptionFormat.NotSupported: - msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list" - raise OptionsReaderError(msg) from None - - if isinstance(setting, bool | int): - return str(setting) - - return setting + match setting: + case {}: + assert isinstance(setting, Mapping) # MyPy 1.15 doesn't narrow this for us + try: + if option_format is None: + raise OptionFormat.NotSupported + return option_format.format_table(setting) + except OptionFormat.NotSupported: + msg = ( + f"Error converting {setting!r} to a string: this setting doesn't accept a table" + ) + raise OptionsReaderError(msg) from None + case bool() | int(): + return str(setting) + case [*_]: + try: + if option_format is None: + raise OptionFormat.NotSupported + return option_format.format_list(setting) + except OptionFormat.NotSupported: + msg = ( + f"Error converting {setting!r} to a string: this setting doesn't accept a list" + ) + raise OptionsReaderError(msg) from None + case _: + assert isinstance(setting, str) # MyPy 1.15 doesn't narrow this for us + return setting class OptionsReader: diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index 1a3d2d274..b0511a2a4 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -387,20 +387,21 @@ def setup_python( env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0") log.step("Installing build tools...") - if build_frontend == "pip": - # No additional build tools required - pass - elif build_frontend == "build": - call( - "pip", - "install", - "--upgrade", - "build[virtualenv]", - *constraint_flags(dependency_constraint), - env=env, - ) - else: - assert_never(build_frontend) + match build_frontend: + case "pip": + # No additional build tools required + pass + case "build": + call( + "pip", + "install", + "--upgrade", + "build[virtualenv]", + *constraint_flags(dependency_constraint), + env=env, + ) + case _: + assert_never(build_frontend) return target_install_path, env @@ -494,34 +495,35 @@ def build(options: Options, tmp_path: Path) -> None: if constraints_path: combine_constraints(build_env, constraints_path, None) - if build_frontend.name == "pip": - # Path.resolve() is needed. Without it pip wheel may try to - # fetch package from pypi.org. See - # https://github.com/pypa/cibuildwheel/pull/369 - call( - "python", - "-m", - "pip", - "wheel", - build_options.package_dir.resolve(), - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *extra_flags, - env=build_env, - ) - elif build_frontend.name == "build": - call( - "python", - "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - *extra_flags, - env=build_env, - ) - else: - assert_never(build_frontend) + match build_frontend.name: + case "pip": + # Path.resolve() is needed. Without it pip wheel may try to + # fetch package from pypi.org. See + # https://github.com/pypa/cibuildwheel/pull/369 + call( + "python", + "-m", + "pip", + "wheel", + build_options.package_dir.resolve(), + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *extra_flags, + env=build_env, + ) + case "build": + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) + case _: + assert_never(build_frontend) test_wheel = built_wheel = next(built_wheel_dir.glob("*.whl")) diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index 82a695e69..50516f8c5 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -275,37 +275,38 @@ def build_in_container( build_frontend, build_options.build_verbosity, build_options.config_settings ) - if build_frontend.name == "pip": - container.call( - [ - "python", - "-m", - "pip", - "wheel", - container_package_dir, - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *extra_flags, - ], - env=env, - ) - elif build_frontend.name == "build" or build_frontend.name == "build[uv]": - if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: - extra_flags += ["--installer=uv"] - container.call( - [ - "python", - "-m", - "build", - container_package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - *extra_flags, - ], - env=env, - ) - else: - assert_never(build_frontend) + match build_frontend.name: + case "pip": + container.call( + [ + "python", + "-m", + "pip", + "wheel", + container_package_dir, + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *extra_flags, + ], + env=env, + ) + case "build" | "build[uv]": + if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: + extra_flags += ["--installer=uv"] + container.call( + [ + "python", + "-m", + "build", + container_package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + *extra_flags, + ], + env=env, + ) + case _: + assert_never(build_frontend) built_wheel = container.glob(built_wheel_dir, "*.whl")[0] diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 99ca915b8..cc0a5a8ef 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -345,39 +345,40 @@ def setup_python( env.setdefault("SDKROOT", arm64_compatible_sdks[0]) log.step("Installing build tools...") - if build_frontend == "pip": - call( - "pip", - "install", - "--upgrade", - "delocate", - *constraint_flags(dependency_constraint), - env=env, - ) - elif build_frontend == "build": - call( - "pip", - "install", - "--upgrade", - "delocate", - "build[virtualenv]", - *constraint_flags(dependency_constraint), - env=env, - ) - elif build_frontend == "build[uv]": - assert uv_path is not None - call( - uv_path, - "pip", - "install", - "--upgrade", - "delocate", - "build[virtualenv, uv]", - *constraint_flags(dependency_constraint), - env=env, - ) - else: - assert_never(build_frontend) + match build_frontend: + case "pip": + call( + "pip", + "install", + "--upgrade", + "delocate", + *constraint_flags(dependency_constraint), + env=env, + ) + case "build": + call( + "pip", + "install", + "--upgrade", + "delocate", + "build[virtualenv]", + *constraint_flags(dependency_constraint), + env=env, + ) + case "build[uv]": + assert uv_path is not None + call( + uv_path, + "pip", + "install", + "--upgrade", + "delocate", + "build[virtualenv, uv]", + *constraint_flags(dependency_constraint), + env=env, + ) + case _: + assert_never(build_frontend) return base_python, env @@ -467,35 +468,40 @@ def build(options: Options, tmp_path: Path) -> None: build_env, constraints_path, identifier_tmp_dir if use_uv else None ) - if build_frontend.name == "pip": - # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org - # see https://github.com/pypa/cibuildwheel/pull/369 - call( - "python", - "-m", - "pip", - "wheel", - build_options.package_dir.resolve(), - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *extra_flags, - env=build_env, - ) - elif build_frontend.name == "build" or build_frontend.name == "build[uv]": - if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: - extra_flags.append("--installer=uv") - call( - "python", - "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - *extra_flags, - env=build_env, - ) - else: - assert_never(build_frontend) + match build_frontend.name: + case "pip": + # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org + # see https://github.com/pypa/cibuildwheel/pull/369 + call( + "python", + "-m", + "pip", + "wheel", + build_options.package_dir.resolve(), + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *extra_flags, + env=build_env, + ) + case "build" | "build[uv]": + if ( + use_uv + and "--no-isolation" not in extra_flags + and "-n" not in extra_flags + ): + extra_flags.append("--installer=uv") + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) + case _: + assert_never(build_frontend) built_wheel = next(built_wheel_dir.glob("*.whl")) diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index c57a53de0..6913092cd 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -305,26 +305,27 @@ def setup_python( call("pip", "--version", env=env) log.step("Installing build tools...") - if build_frontend == "build": - call( - "pip", - "install", - "--upgrade", - "build[virtualenv]", - *constraint_flags(dependency_constraint), - env=env, - ) - elif build_frontend == "build[uv]": - assert uv_path is not None - call( - uv_path, - "pip", - "install", - "--upgrade", - "build[virtualenv]", - *constraint_flags(dependency_constraint), - env=env, - ) + match build_frontend: + case "build": + call( + "pip", + "install", + "--upgrade", + "build[virtualenv]", + *constraint_flags(dependency_constraint), + env=env, + ) + case "build[uv]": + assert uv_path is not None + call( + uv_path, + "pip", + "install", + "--upgrade", + "build[virtualenv]", + *constraint_flags(dependency_constraint), + env=env, + ) if python_libs_base: # Set up the environment for various backends to enable cross-compilation @@ -467,36 +468,41 @@ def build(options: Options, tmp_path: Path) -> None: if constraints_path: combine_constraints(build_env, constraints_path, identifier_tmp_dir) - if build_frontend.name == "pip": - # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org - # see https://github.com/pypa/cibuildwheel/pull/369 - call( - "python", - "-m", - "pip", - "wheel", - options.globals.package_dir.resolve(), - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *extra_flags, - env=build_env, - ) - elif build_frontend.name == "build" or build_frontend.name == "build[uv]": - if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: - extra_flags.append("--installer=uv") - - call( - "python", - "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - *extra_flags, - env=build_env, - ) - else: - assert_never(build_frontend) + match build_frontend.name: + case "pip": + # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org + # see https://github.com/pypa/cibuildwheel/pull/369 + call( + "python", + "-m", + "pip", + "wheel", + options.globals.package_dir.resolve(), + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *extra_flags, + env=build_env, + ) + case "build" | "build[uv]": + if ( + use_uv + and "--no-isolation" not in extra_flags + and "-n" not in extra_flags + ): + extra_flags.append("--installer=uv") + + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) + case _: + assert_never(build_frontend) built_wheel = next(built_wheel_dir.glob("*.whl")) diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index 3fcebe535..f9e2ef3b9 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -14,32 +14,16 @@ def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None: def is_main(parent: ast.AST | None) -> bool: - if parent is None: - return False - - # This would be much nicer with 3.10's pattern matching! - if not isinstance(parent, ast.If): - return False - if not isinstance(parent.test, ast.Compare): - return False - - try: - (op,) = parent.test.ops - (comp,) = parent.test.comparators - except ValueError: - return False - - if not isinstance(op, ast.Eq): - return False - - values = {comp, parent.test.left} - - mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"} - if len(mains) != 1: - return False - consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"} - - return len(consts) == 1 + match parent: + case ast.If(test=ast.Compare(left=left, ops=[ast.Eq()], comparators=[comp])): + values = {left, comp} + mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"} + if len(mains) != 1: + return False + consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"} + return len(consts) == 1 + case _: + return False class Analyzer(ast.NodeVisitor): @@ -65,12 +49,10 @@ def visit_keyword(self, node: ast.keyword) -> None: parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3)) ) - if ( - node.arg == "python_requires" - and isinstance(node.value, ast.Constant) - and (unnested or name_main_unnested) - ): - self.requires_python = node.value.value + match node: + case ast.keyword(arg="python_requires", value=ast.Constant(value=version)): + if unnested or name_main_unnested: + self.requires_python = version def setup_py_python_requires(content: str) -> str | None: