diff --git a/bin/generate_schema.py b/bin/generate_schema.py index 4c127d181..7308b4ba4 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -318,6 +318,8 @@ def build_frontend_schema( case {"type": "string_table_array"}: del value["type"] value["oneOf"] = string_table_array + case _: + pass overrides = yaml.safe_load( """ diff --git a/bin/update_pythons.py b/bin/update_pythons.py index dd6bb38cc..72cad1f4c 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -76,9 +76,9 @@ def __init__(self, arch_str: ArchStr, free_threaded: bool) -> None: response.raise_for_status() api_info = response.json() - for resource in api_info["resources"]: - if resource["@type"] == "PackageBaseAddress/3.0.0": - endpoint = resource["@id"] + endpoint = next( + r["@id"] for r in api_info["resources"] if r["@type"] == "PackageBaseAddress/3.0.0" + ) ARCH_DICT = {"32": "win32", "64": "win_amd64", "ARM64": "win_arm64"} PACKAGE_DICT = {"32": "pythonx86", "64": "python", "ARM64": "pythonarm64"} diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 49e14a93c..e0844f974 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -546,9 +546,14 @@ def call( else: output_io = sys.stdout.buffer - while True: + return_code = None + while return_code is None: line = self.bash_stdout.readline() + if not line: + msg = "Failed to read the return code, the container shell has exited" + raise RuntimeError(msg) + if line.endswith(bytes(end_of_message, encoding="utf8") + b"\n"): # fmt: off footer_offset = ( @@ -563,7 +568,6 @@ def call( # add the last line to output, without the footer output_io.write(line[0:footer_offset]) output_io.flush() - break else: output_io.write(line) output_io.flush() diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index 72ab35952..2d1fefd64 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -540,7 +540,7 @@ def build(options: Options, tmp_path: Path) -> None: f"that is compatible with {config.identifier}. " "Skipping build step..." ) - test_wheel = compatible_wheel + repaired_wheel = compatible_wheel else: if build_options.before_build: log.step("Running before_build...") @@ -632,8 +632,6 @@ def build(options: Options, tmp_path: Path) -> None: run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel) - test_wheel = repaired_wheel - if build_options.test_command and build_options.test_selector(config.identifier): if not config.is_simulator: log.step("Skipping tests on non-simulator SDK") @@ -693,7 +691,7 @@ def build(options: Options, tmp_path: Path) -> None: platform_tag, "--target", testbed_path / "iOSTestbed" / "app_packages", - f"{test_wheel}{build_options.test_extras}", + f"{repaired_wheel}{build_options.test_extras}", *build_options.test_requires, env=test_env, ) diff --git a/cibuildwheel/platforms/pyodide.py b/cibuildwheel/platforms/pyodide.py index d4657c769..6de8a00db 100644 --- a/cibuildwheel/platforms/pyodide.py +++ b/cibuildwheel/platforms/pyodide.py @@ -473,7 +473,7 @@ def build(options: Options, tmp_path: Path) -> None: print( f"\nFound previously built wheel {compatible_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) - built_wheel = compatible_wheel + repaired_wheel = compatible_wheel else: if build_options.before_build: log.step("Running before_build...") diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index c45d7fa49..185d2a74d 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -60,6 +60,8 @@ def visit_keyword(self, node: ast.keyword) -> None: case ast.keyword(arg="python_requires", value=ast.Constant(value=str() as version)): if unnested or name_main_unnested: self.requires_python = version + case _: + pass def setup_py_python_requires(content: str) -> str | None: diff --git a/docs/main.py b/docs/main.py index ce79df3a4..cb0026ba0 100644 --- a/docs/main.py +++ b/docs/main.py @@ -12,7 +12,7 @@ def define_env(env: Any) -> None: # noqa: ANN401 "Hook function for mkdocs-macros" @env.macro # type: ignore[untyped-decorator] - def subprocess_run(*args: str) -> str: + def subprocess_run(*args: str) -> str: # type: ignore[misc] "Run a subprocess and return the stdout" env = os.environ.copy() scripts = sysconfig.get_path("scripts") diff --git a/pyproject.toml b/pyproject.toml index 69e771db1..8aba9f5d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,10 +116,27 @@ files = [ "bin/*.py", "noxfile.py", ] +disallow_any_decorated = true +disallow_any_unimported = true +disallow_untyped_globals = true +disallow_redefinition = true warn_unused_configs = true strict = true -enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +enable_error_code = [ + "deprecated", + "exhaustive-match", + "ignore-without-code", + "mutable-override", + "possibly-undefined", + "redundant-expr", + "redundant-self", + "truthy-bool", + "truthy-iterable", + "unimported-reveal", + "unused-awaitable", +] warn_unreachable = false +native_parser = true [[tool.mypy.overrides]] module = [ @@ -130,6 +147,10 @@ module = [ ] ignore_missing_imports = true +[[tool.mypy.overrides]] +module = ["cibuildwheel.bashlex_eval"] +disable_error_code = ["no-any-unimported"] + [tool.pylint] py-version = "3.11" diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index 8b94187c7..5e931976c 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -29,6 +29,7 @@ TYPE_CHECKING = False if TYPE_CHECKING: from collections.abc import Iterator + from typing import Any # Test utilities @@ -84,6 +85,18 @@ def test_no_lf(container_engine: OCIContainerEngineConfig) -> None: assert container.call(["printf", "hello"], capture_output=True) == "hello" +def test_abnormal_exit(container_engine: OCIContainerEngineConfig) -> None: + container = OCIContainer( + engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM + ) + with container: + # kill the shell without a newline, so the write below still goes + # through the same buffer and the call fails on read, not on write + container.bash_stdin.write(b"exit") + with pytest.raises(RuntimeError): + container.call(["echo", "hello"]) + + def test_debug_info(container_engine: OCIContainerEngineConfig) -> None: container = OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM @@ -139,6 +152,7 @@ def test_container_removed(container_engine: OCIContainerEngineConfig) -> None: ) as container: assert container.name is not None container_name = container.name + docker_containers_listing = "" for _ in range(timeout): docker_containers_listing = subprocess.run( f"{container.engine.name} container ls", @@ -330,7 +344,7 @@ def test_podman_vfs( # This requires that we write configuration files and point to them # with environment variables before we run podman # https://github.com/containers/common/blob/main/docs/containers.conf.5.md - vfs_containers_conf_data = { + vfs_containers_conf_data: dict[str, dict[str, Any]] = { "containers": { "default_capabilities": [ "CHOWN", @@ -348,6 +362,20 @@ def test_podman_vfs( }, "engine": {"cgroup_manager": "cgroupfs", "events_logger": "file"}, } + + # Setting CONTAINERS_CONF makes podman ignore its usual config files, so + # carry over the default OCI runtime; the fallback found on PATH can be + # too old for the OCI spec version podman generates (e.g. Ubuntu 24.04's + # crun 1.14.1 with podman 5.x). + oci_runtime = subprocess.run( + ["podman", "info", "--format", "{{.Host.OCIRuntime.Path}}"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + runtime_name = Path(oci_runtime).name + vfs_containers_conf_data["engine"]["runtime"] = runtime_name + vfs_containers_conf_data["engine"]["runtimes"] = {runtime_name: [oci_runtime]} # https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md storage_root = vfs_path / ".local/share/containers/vfs-storage" run_root = vfs_path / ".local/share/containers/vfs-runroot"