From febff41476fbee88c6209bcd0a37163aa8654962 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 30 Oct 2024 14:56:05 -0400 Subject: [PATCH 1/5] feat: support test-groups Signed-off-by: Henry Schreiner --- .pre-commit-config.yaml | 1 + bin/generate_schema.py | 3 ++ cibuildwheel/options.py | 22 ++++++++-- cibuildwheel/projectfiles.py | 29 ++++++++++--- .../resources/cibuildwheel.schema.json | 30 +++++++++++++ cibuildwheel/resources/defaults.toml | 1 + docs/options.md | 34 +++++++++++++++ pyproject.toml | 1 + unit_test/options_toml_test.py | 8 ++++ unit_test/projectfiles_test.py | 43 +++++++++++++------ 10 files changed, 148 insertions(+), 24 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index afc8b6f19..590455bd3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,6 +29,7 @@ repos: args: ["--python-version=3.8"] additional_dependencies: &mypy-dependencies - bracex + - dependency-groups>=1.2 - nox - orjson - packaging diff --git a/bin/generate_schema.py b/bin/generate_schema.py index 41f4ad4cd..fa03284a2 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -164,6 +164,9 @@ test-extras: description: Install your wheel for testing using `extras_require` type: string_array + test-groups: + description: Install extra groups when testing + type: string_array test-requires: description: Install Python dependencies before running the tests type: string_array diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 5e1e71d16..df2442a59 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -22,7 +22,7 @@ from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .logger import log from .oci_container import OCIContainerEngineConfig -from .projectfiles import get_requires_python_str +from .projectfiles import get_dependency_groups, get_requires_python_str from .typing import PLATFORMS, PlatformName from .util import ( MANYLINUX_ARCHS, @@ -92,6 +92,7 @@ class BuildOptions: before_test: str | None test_requires: list[str] test_extras: str + test_groups: list[str] build_verbosity: int build_frontend: BuildFrontendConfig | None config_settings: str @@ -568,6 +569,13 @@ def __init__( disallow=DISALLOWED_OPTIONS, ) + self.project_dir = Path(command_line_arguments.package_dir) + try: + with self.project_dir.joinpath("pyproject.toml").open("rb") as f: + self.pyproject_toml = tomllib.load(f) + except FileNotFoundError: + self.pyproject_toml = {} + @property def config_file_path(self) -> Path | None: args = self.command_line_arguments @@ -584,8 +592,10 @@ def config_file_path(self) -> Path | None: @functools.cached_property def package_requires_python_str(self) -> str | None: - args = self.command_line_arguments - return get_requires_python_str(Path(args.package_dir)) + return get_requires_python_str(self.project_dir, self.pyproject_toml) + + def dependency_groups(self, *groups: str) -> tuple[str, ...]: + return get_dependency_groups(self.pyproject_toml, *groups) @property def globals(self) -> GlobalOptions: @@ -672,6 +682,9 @@ def build_options(self, identifier: str | None) -> BuildOptions: "test-requires", option_format=ListFormat(sep=" ") ).split() 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] + test_dependency_groups = self.dependency_groups(*test_groups) build_verbosity_str = self.reader.get("build-verbosity") build_frontend_str = self.reader.get( @@ -771,8 +784,9 @@ def build_options(self, identifier: str | None) -> BuildOptions: return BuildOptions( globals=self.globals, test_command=test_command, - test_requires=test_requires, + test_requires=[*test_requires, *test_dependency_groups], test_extras=test_extras, + test_groups=test_groups, before_test=before_test, before_build=before_build, before_all=before_all, diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index c1a376696..e46520c4d 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -4,8 +4,9 @@ import configparser import contextlib from pathlib import Path +from typing import Any -from ._compat import tomllib +import dependency_groups def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None: @@ -84,15 +85,12 @@ def setup_py_python_requires(content: str) -> str | None: return None -def get_requires_python_str(package_dir: Path) -> str | None: +def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any]) -> str | None: """Return the python requires string from the most canonical source available, or None""" # Read in from pyproject.toml:project.requires-python - with contextlib.suppress(FileNotFoundError): - with (package_dir / "pyproject.toml").open("rb") as f1: - info = tomllib.load(f1) - with contextlib.suppress(KeyError, IndexError, TypeError): - return str(info["project"]["requires-python"]) + with contextlib.suppress(KeyError, IndexError, TypeError): + return str(pyproject_toml["project"]["requires-python"]) # Read in from setup.cfg:options.python_requires config = configparser.ConfigParser() @@ -106,3 +104,20 @@ def get_requires_python_str(package_dir: Path) -> str | None: return setup_py_python_requires(f2.read()) return None + + +def get_dependency_groups(pyproject_toml: dict[str, Any], *groups: str) -> tuple[str, ...]: + """ + Get the packages in dependency-groups for a package. + """ + + if not groups: + return () + + try: + dependency_groups_toml = pyproject_toml["dependency-groups"] + except KeyError: + msg = f"Didn't find [dependency-groups], which are needed to resolve {groups!r}." + raise KeyError(msg) from None + + return dependency_groups.resolve(dependency_groups_toml, *groups) diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index 8e2508bc9..92f368b18 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -397,6 +397,21 @@ ], "title": "CIBW_TEST_EXTRAS" }, + "test-groups": { + "description": "Install extra groups when testing", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "title": "CIBW_TEST_GROUPS" + }, "test-requires": { "description": "Install Python dependencies before running the tests", "oneOf": [ @@ -571,6 +586,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -675,6 +693,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -720,6 +741,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -778,6 +802,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -823,6 +850,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 21bac7a0c..f7839643e 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -20,6 +20,7 @@ test-command = "" before-test = "" test-requires = [] test-extras = [] +test-groups = [] container-engine = "docker" diff --git a/docs/options.md b/docs/options.md index 37f54bfab..5ed692d63 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1604,6 +1604,40 @@ Platform-specific environment variables are also available:
In configuration files, you can use an inline array, and the items will be joined with a comma. + +### `CIBW_TEST_GROUPS` {: #test-groups} +> Install your wheel for testing using `dependency-groups` + +List of +[dependency-groups](https://peps.python.org/pep-0735) +that should be included when installing the wheel prior to running the +tests. This can be used to avoid having to redefine test dependencies in +`CIBW_TEST_REQUIRES` if they are already defined in `pyproject.toml`. + +Platform-specific environment variables are also available:
+`CIBW_TEST_GROUPS_MACOS` | `CIBW_TEST_GROUPS_WINDOWS` | `CIBW_TEST_GROUPS_LINUX` | `CIBW_TEST_GROUPS_PYODIDE` + +#### Examples + +!!! tab examples "Environment variables" + + ```yaml + # Will cause the wheel to be installed with these groups of dependencies + CIBW_TEST_GROUPS: "test,qt" + ``` + + Separate multiple items with a comma. + +!!! tab examples "pyproject.toml" + + ```toml + [tool.cibuildwheel] + # Will cause the wheel to be installed with these groups of dependencies + test-groups = ["test", "qt"] + ``` + + In configuration files, you can use an inline array, and the items will be joined with a comma. + ### `CIBW_TEST_SKIP` {: #test-skip} > Skip running tests on some builds diff --git a/pyproject.toml b/pyproject.toml index 2dd8588f6..f31708d30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dependencies = [ "bashlex!=0.13", "bracex", "certifi", + "dependency-groups>=1.2", "filelock", "packaging>=20.9", "platformdirs", diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index da21d884b..584c272d8 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -22,6 +22,7 @@ test-command = "pyproject" test-requires = "something" test-extras = ["one", "two"] +test-groups = ["three", "four"] manylinux-x86_64-image = "manylinux1" @@ -60,6 +61,7 @@ def test_simple_settings(tmp_path, platform, fname): == 'THING="OTHER" FOO="BAR"' ) assert options_reader.get("test-extras", option_format=ListFormat(",")) == "one,two" + assert options_reader.get("test-groups", option_format=ListFormat(",")) == "three,four" assert options_reader.get("manylinux-x86_64-image") == "manylinux1" assert options_reader.get("manylinux-i686-image") == "manylinux2014" @@ -85,7 +87,9 @@ def test_envvar_override(tmp_path, platform): "CIBW_MANYLINUX_X86_64_IMAGE": "manylinux_2_24", "CIBW_TEST_COMMAND": "mytest", "CIBW_TEST_REQUIRES": "docs", + "CIBW_TEST_GROUPS": "mgroup,two", "CIBW_TEST_REQUIRES_LINUX": "scod", + "CIBW_TEST_GROUPS_LINUX": "lgroup", }, ) @@ -99,6 +103,10 @@ def test_envvar_override(tmp_path, platform): options_reader.get("test-requires", option_format=ListFormat(" ")) == {"windows": "docs", "macos": "docs", "linux": "scod"}[platform] ) + assert ( + options_reader.get("test-groups", option_format=ListFormat(",")) + == {"windows": "mgroup,two", "macos": "mgroup,two", "linux": "lgroup"}[platform] + ) assert options_reader.get("test-command") == "mytest" diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index b1839eda3..08f511c4c 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -2,7 +2,12 @@ from textwrap import dedent -from cibuildwheel.projectfiles import get_requires_python_str, setup_py_python_requires +from cibuildwheel._compat import tomllib +from cibuildwheel.projectfiles import ( + get_dependency_groups, + get_requires_python_str, + setup_py_python_requires, +) def test_read_setup_py_simple(tmp_path): @@ -23,7 +28,7 @@ def test_read_setup_py_simple(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_main(tmp_path): @@ -45,7 +50,7 @@ def test_read_setup_py_if_main(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_main_reversed(tmp_path): @@ -67,7 +72,7 @@ def test_read_setup_py_if_main_reversed(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_invalid(tmp_path): @@ -89,7 +94,7 @@ def test_read_setup_py_if_invalid(tmp_path): ) assert not setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) - assert not get_requires_python_str(tmp_path) + assert not get_requires_python_str(tmp_path, {}) def test_read_setup_py_full(tmp_path): @@ -115,7 +120,7 @@ def test_read_setup_py_full(tmp_path): assert ( setup_py_python_requires(tmp_path.joinpath("setup.py").read_text(encoding="utf8")) == "1.24" ) - assert get_requires_python_str(tmp_path) == "1.24" + assert get_requires_python_str(tmp_path, {}) == "1.24" def test_read_setup_py_assign(tmp_path): @@ -138,7 +143,7 @@ def test_read_setup_py_assign(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_py_None(tmp_path): @@ -161,7 +166,7 @@ def test_read_setup_py_None(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_py_empty(tmp_path): @@ -183,7 +188,7 @@ def test_read_setup_py_empty(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_cfg(tmp_path): @@ -199,7 +204,7 @@ def test_read_setup_cfg(tmp_path): ) ) - assert get_requires_python_str(tmp_path) == "1.234" + assert get_requires_python_str(tmp_path, {}) == "1.234" def test_read_setup_cfg_empty(tmp_path): @@ -215,7 +220,7 @@ def test_read_setup_cfg_empty(tmp_path): ) ) - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_pyproject_toml(tmp_path): @@ -231,8 +236,10 @@ def test_read_pyproject_toml(tmp_path): """ ) ) + with open(tmp_path / "pyproject.toml", "rb") as f: + pyproject_toml = tomllib.load(f) - assert get_requires_python_str(tmp_path) == "1.654" + assert get_requires_python_str(tmp_path, pyproject_toml) == "1.654" def test_read_pyproject_toml_empty(tmp_path): @@ -245,5 +252,15 @@ def test_read_pyproject_toml_empty(tmp_path): """ ) ) + with open(tmp_path / "pyproject.toml", "rb") as f: + pyproject_toml = tomllib.load(f) - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, pyproject_toml) is None + + +def test_read_dep_groups(): + pyproject_toml = {"dependency-groups": {"group1": ["pkg1", "pkg2"], "group2": ["pkg3"]}} + assert get_dependency_groups(pyproject_toml) == () + assert get_dependency_groups(pyproject_toml, "group1") == ("pkg1", "pkg2") + assert get_dependency_groups(pyproject_toml, "group2") == ("pkg3",) + assert get_dependency_groups(pyproject_toml, "group1", "group2") == ("pkg1", "pkg2", "pkg3") From 15be4d610faa2cfe7eefd6c73651aa07a6609317 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 1 Nov 2024 09:54:36 -0400 Subject: [PATCH 2/5] refactor: address review comments Signed-off-by: Henry Schreiner --- cibuildwheel/options.py | 21 ++++++++++----------- cibuildwheel/projectfiles.py | 2 +- docs/options.md | 2 +- unit_test/options_toml_test.py | 2 +- unit_test/projectfiles_test.py | 10 +++++----- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index df2442a59..5b36c7722 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -22,7 +22,7 @@ from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .logger import log from .oci_container import OCIContainerEngineConfig -from .projectfiles import get_dependency_groups, get_requires_python_str +from .projectfiles import get_requires_python_str, resolve_dependency_groups from .typing import PLATFORMS, PlatformName from .util import ( MANYLINUX_ARCHS, @@ -569,9 +569,9 @@ def __init__( disallow=DISALLOWED_OPTIONS, ) - self.project_dir = Path(command_line_arguments.package_dir) + self.package_dir = Path(command_line_arguments.package_dir) try: - with self.project_dir.joinpath("pyproject.toml").open("rb") as f: + with self.package_dir.joinpath("pyproject.toml").open("rb") as f: self.pyproject_toml = tomllib.load(f) except FileNotFoundError: self.pyproject_toml = {} @@ -592,10 +592,7 @@ def config_file_path(self) -> Path | None: @functools.cached_property def package_requires_python_str(self) -> str | None: - return get_requires_python_str(self.project_dir, self.pyproject_toml) - - def dependency_groups(self, *groups: str) -> tuple[str, ...]: - return get_dependency_groups(self.pyproject_toml, *groups) + return get_requires_python_str(self.package_dir, self.pyproject_toml) @property def globals(self) -> GlobalOptions: @@ -682,9 +679,11 @@ def build_options(self, identifier: str | None) -> BuildOptions: "test-requires", option_format=ListFormat(sep=" ") ).split() 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] - test_dependency_groups = self.dependency_groups(*test_groups) + test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" ")) + test_groups = [x for x in test_groups_str.split() if x] + test_requirements_from_groups = resolve_dependency_groups( + self.pyproject_toml, *test_groups + ) build_verbosity_str = self.reader.get("build-verbosity") build_frontend_str = self.reader.get( @@ -784,7 +783,7 @@ def build_options(self, identifier: str | None) -> BuildOptions: return BuildOptions( globals=self.globals, test_command=test_command, - test_requires=[*test_requires, *test_dependency_groups], + test_requires=[*test_requires, *test_requirements_from_groups], test_extras=test_extras, test_groups=test_groups, before_test=before_test, diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index e46520c4d..341ffae3f 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -106,7 +106,7 @@ def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any]) - return None -def get_dependency_groups(pyproject_toml: dict[str, Any], *groups: str) -> tuple[str, ...]: +def resolve_dependency_groups(pyproject_toml: dict[str, Any], *groups: str) -> tuple[str, ...]: """ Get the packages in dependency-groups for a package. """ diff --git a/docs/options.md b/docs/options.md index 5ed692d63..5e4a4851b 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1606,7 +1606,7 @@ Platform-specific environment variables are also available:
### `CIBW_TEST_GROUPS` {: #test-groups} -> Install your wheel for testing using `dependency-groups` +> Specify test dependencies from your project's `dependency-groups` List of [dependency-groups](https://peps.python.org/pep-0735) diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index 584c272d8..239ee89c0 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -61,7 +61,7 @@ def test_simple_settings(tmp_path, platform, fname): == 'THING="OTHER" FOO="BAR"' ) assert options_reader.get("test-extras", option_format=ListFormat(",")) == "one,two" - assert options_reader.get("test-groups", option_format=ListFormat(",")) == "three,four" + assert options_reader.get("test-groups", option_format=ListFormat(" ")) == "three four" assert options_reader.get("manylinux-x86_64-image") == "manylinux1" assert options_reader.get("manylinux-i686-image") == "manylinux2014" diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index 08f511c4c..9961fa537 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -4,8 +4,8 @@ from cibuildwheel._compat import tomllib from cibuildwheel.projectfiles import ( - get_dependency_groups, get_requires_python_str, + resolve_dependency_groups, setup_py_python_requires, ) @@ -260,7 +260,7 @@ def test_read_pyproject_toml_empty(tmp_path): def test_read_dep_groups(): pyproject_toml = {"dependency-groups": {"group1": ["pkg1", "pkg2"], "group2": ["pkg3"]}} - assert get_dependency_groups(pyproject_toml) == () - assert get_dependency_groups(pyproject_toml, "group1") == ("pkg1", "pkg2") - assert get_dependency_groups(pyproject_toml, "group2") == ("pkg3",) - assert get_dependency_groups(pyproject_toml, "group1", "group2") == ("pkg1", "pkg2", "pkg3") + assert resolve_dependency_groups(pyproject_toml) == () + assert resolve_dependency_groups(pyproject_toml, "group1") == ("pkg1", "pkg2") + assert resolve_dependency_groups(pyproject_toml, "group2") == ("pkg3",) + assert resolve_dependency_groups(pyproject_toml, "group1", "group2") == ("pkg1", "pkg2", "pkg3") From 6aa8e25c1baea7a370aee832937e0e8ad6cd5cb2 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 1 Nov 2024 10:05:36 -0400 Subject: [PATCH 3/5] tests: add a integration test Signed-off-by: Henry Schreiner --- test/test_testing.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test_testing.py b/test/test_testing.py index a31dae514..b94e3ee90 100644 --- a/test/test_testing.py +++ b/test/test_testing.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect import os import subprocess import textwrap @@ -114,6 +115,38 @@ def test_extras_require(tmp_path): assert set(actual_wheels) == set(expected_wheels) +def test_dependency_groups(tmp_path): + group_project = project_with_a_test.copy() + group_project.files["pyproject.toml"] = inspect.cleandoc(""" + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + + [dependency-groups] + dev = ["pytest"] + """) + + project_dir = tmp_path / "project" + group_project.generate(project_dir) + + # build and test the wheels + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_TEST_GROUPS": "dev", + # the 'false ||' bit is to ensure this command runs in a shell on + # mac/linux. + "CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test", + "CIBW_TEST_COMMAND_WINDOWS": "COLOR 00 || pytest {project}/test", + }, + single_python=True, + ) + + # also check that we got the right wheels + expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True) + assert set(actual_wheels) == set(expected_wheels) + + project_with_a_failing_test = test_projects.new_c_project() project_with_a_failing_test.files["test/spam_test.py"] = r""" from unittest import TestCase From 623ca83b6a9bcdc011bb2fc13affe53d923918ed Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 2 Nov 2024 10:55:00 -0400 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Matthieu Darbois --- docs/options.md | 6 +++--- unit_test/options_toml_test.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/options.md b/docs/options.md index 5e4a4851b..0225c8dde 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1623,10 +1623,10 @@ Platform-specific environment variables are also available:
```yaml # Will cause the wheel to be installed with these groups of dependencies - CIBW_TEST_GROUPS: "test,qt" + CIBW_TEST_GROUPS: "test qt" ``` - Separate multiple items with a comma. + Separate multiple items with a space. !!! tab examples "pyproject.toml" @@ -1636,7 +1636,7 @@ Platform-specific environment variables are also available:
test-groups = ["test", "qt"] ``` - In configuration files, you can use an inline array, and the items will be joined with a comma. + In configuration files, you can use an inline array, and the items will be joined with a space. ### `CIBW_TEST_SKIP` {: #test-skip} > Skip running tests on some builds diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index 239ee89c0..5eda6cbb0 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -87,7 +87,7 @@ def test_envvar_override(tmp_path, platform): "CIBW_MANYLINUX_X86_64_IMAGE": "manylinux_2_24", "CIBW_TEST_COMMAND": "mytest", "CIBW_TEST_REQUIRES": "docs", - "CIBW_TEST_GROUPS": "mgroup,two", + "CIBW_TEST_GROUPS": "mgroup two", "CIBW_TEST_REQUIRES_LINUX": "scod", "CIBW_TEST_GROUPS_LINUX": "lgroup", }, @@ -104,8 +104,8 @@ def test_envvar_override(tmp_path, platform): == {"windows": "docs", "macos": "docs", "linux": "scod"}[platform] ) assert ( - options_reader.get("test-groups", option_format=ListFormat(",")) - == {"windows": "mgroup,two", "macos": "mgroup,two", "linux": "lgroup"}[platform] + options_reader.get("test-groups", option_format=ListFormat(" ")) + == {"windows": "mgroup two", "macos": "mgroup two", "linux": "lgroup"}[platform] ) assert options_reader.get("test-command") == "mytest" From 7338bf928f0c2a11956c5454fd3fddf642bd4afb Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 3 Nov 2024 16:49:18 -0500 Subject: [PATCH 5/5] fix: better error messages based on feedback Signed-off-by: Henry Schreiner --- cibuildwheel/options.py | 4 +++- cibuildwheel/projectfiles.py | 14 ++++++++++---- unit_test/projectfiles_test.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 5b36c7722..455e1ee71 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -551,6 +551,8 @@ def get( class Options: + pyproject_toml: dict[str, Any] | None + def __init__( self, platform: PlatformName, @@ -574,7 +576,7 @@ def __init__( with self.package_dir.joinpath("pyproject.toml").open("rb") as f: self.pyproject_toml = tomllib.load(f) except FileNotFoundError: - self.pyproject_toml = {} + self.pyproject_toml = None @property def config_file_path(self) -> Path | None: diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index 341ffae3f..d2fd635e0 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -85,12 +85,12 @@ def setup_py_python_requires(content: str) -> str | None: return None -def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any]) -> str | None: +def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any] | None) -> str | None: """Return the python requires string from the most canonical source available, or None""" # Read in from pyproject.toml:project.requires-python with contextlib.suppress(KeyError, IndexError, TypeError): - return str(pyproject_toml["project"]["requires-python"]) + return str((pyproject_toml or {})["project"]["requires-python"]) # Read in from setup.cfg:options.python_requires config = configparser.ConfigParser() @@ -106,7 +106,9 @@ def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any]) - return None -def resolve_dependency_groups(pyproject_toml: dict[str, Any], *groups: str) -> tuple[str, ...]: +def resolve_dependency_groups( + pyproject_toml: dict[str, Any] | None, *groups: str +) -> tuple[str, ...]: """ Get the packages in dependency-groups for a package. """ @@ -114,10 +116,14 @@ def resolve_dependency_groups(pyproject_toml: dict[str, Any], *groups: str) -> t if not groups: return () + if pyproject_toml is None: + msg = f"Didn't find a pyproject.toml, so can't read [dependency-groups] {groups!r} from it!" + raise FileNotFoundError(msg) + try: dependency_groups_toml = pyproject_toml["dependency-groups"] except KeyError: - msg = f"Didn't find [dependency-groups], which are needed to resolve {groups!r}." + msg = f"Didn't find [dependency-groups] in pyproject.toml, which is needed to resolve {groups!r}." raise KeyError(msg) from None return dependency_groups.resolve(dependency_groups_toml, *groups) diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index 9961fa537..179f648ef 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -2,6 +2,8 @@ from textwrap import dedent +import pytest + from cibuildwheel._compat import tomllib from cibuildwheel.projectfiles import ( get_requires_python_str, @@ -264,3 +266,13 @@ def test_read_dep_groups(): assert resolve_dependency_groups(pyproject_toml, "group1") == ("pkg1", "pkg2") assert resolve_dependency_groups(pyproject_toml, "group2") == ("pkg3",) assert resolve_dependency_groups(pyproject_toml, "group1", "group2") == ("pkg1", "pkg2", "pkg3") + + +def test_dep_group_no_file_error(): + with pytest.raises(FileNotFoundError, match="pyproject.toml"): + resolve_dependency_groups(None, "test") + + +def test_dep_group_no_section_error(): + with pytest.raises(KeyError, match="pyproject.toml"): + resolve_dependency_groups({}, "test")