diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 2d3e32a3..8dd21a1a 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -833,9 +833,11 @@ class Options: msg = f"Failed to parse test runtime config. {e}" raise errors.ConfigurationError(msg) from e - test_requires = self.reader.get( - "test-requires", option_format=ListFormat(sep=" ") - ).split() + test_requires = shlex.split( + self.reader.get( + "test-requires", option_format=ListFormat(sep=" ", quote=shlex.quote) + ) + ) test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=",")) test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" ")) test_groups = [x for x in test_groups_str.split() if x] @@ -932,9 +934,11 @@ class Options: ) audit_command = audit_command_str.split(" && ") if audit_command_str else [] - audit_requires = self.reader.get( - "audit-requires", option_format=ListFormat(sep=" ") - ).split() + audit_requires = shlex.split( + self.reader.get( + "audit-requires", option_format=ListFormat(sep=" ", quote=shlex.quote) + ) + ) return BuildOptions( globals=self.globals, diff --git a/docs/options.md b/docs/options.md index 2e17043c..542d12ba 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1670,6 +1670,10 @@ Platform-specific environment variables are also available:
# Install specific versions of test dependencies [tool.cibuildwheel] test-requires = ["pytest==8.2.2", "packaging==24.1"] + + # Dependency specifiers with environment markers are supported + [tool.cibuildwheel] + test-requires = ["pytest", "pyzstd; python_version >= '3.14'"] ``` In configuration files, you can use an array, and the items will be joined with a space. @@ -1682,6 +1686,10 @@ Platform-specific environment variables are also available:
# Install specific versions of test dependencies CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1 + + # Use shell-style quoting around specifiers containing spaces, such as + # those with environment markers + CIBW_TEST_REQUIRES: "pytest 'pyzstd; python_version >= \"3.14\"'" ``` diff --git a/test/test_android.py b/test/test_android.py index 4037b004..59cec2c2 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -405,7 +405,11 @@ def test_environment_markers(tmp_path: Path) -> None: **cp313_env, "CIBW_TEST_COMMAND": f"python -m pytest {test_filename}", "CIBW_TEST_SOURCES": test_filename, - "CIBW_TEST_REQUIRES": "pytest certifi;sys_platform=='android' platformdirs;sys_platform!='android'", + "CIBW_TEST_REQUIRES": ( + "pytest" + " 'certifi; sys_platform == \"android\"'" + " 'platformdirs; sys_platform != \"android\"'" + ), }, ) diff --git a/unit_test/options_test.py b/unit_test/options_test.py index 6b586038..aafeb161 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -106,6 +106,35 @@ def test_options_1(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: assert local.pyodide_version == "0.29.4" +def test_test_and_audit_requires_with_dependency_specifiers(tmp_path: Path) -> None: + """Regression test for https://github.com/pypa/cibuildwheel/issues/2912""" + pyproject_toml = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + """ +[tool.cibuildwheel] +test-requires = [ + "pytest", + "pyzstd; python_version >= '3.14'", + "zarr>=3", +] +audit-requires = ["abi3audit; python_version >= '3.9'"] +""" + ) + + args = CommandLineArguments.defaults() + args.package_dir = tmp_path + + options = Options(platform="linux", command_line_arguments=args, env={}) + build_options = options.build_options(identifier=None) + + assert build_options.test_requires == [ + "pytest", + "pyzstd; python_version >= '3.14'", + "zarr>=3", + ] + assert build_options.audit_requires == ["abi3audit; python_version >= '3.9'"] + + def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: with tmp_path.joinpath("pyproject.toml").open("w") as f: f.write(PYPROJECT_1)