From cb7686ae748d8b6783b49798ea293b3974a5902e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 22 Dec 2025 13:00:58 -0500 Subject: [PATCH 1/5] tests: capture output when downloading wheels Signed-off-by: Henry Schreiner --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c197d786..0c08eaa9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,8 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path: f"{BASE}", ], check=True, + capture_output=True, + text=True, ) packages = [ "build", From f22883dd46cd5f98e000277ffc005d48e900cb6f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 22 Dec 2025 16:43:43 -0500 Subject: [PATCH 2/5] tests: rework wheelhouse download Signed-off-by: Henry Schreiner --- pyproject.toml | 9 ++---- tests/conftest.py | 55 ++++++++++--------------------- tests/utils/download_wheels.py | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 44 deletions(-) create mode 100644 tests/utils/download_wheels.py diff --git a/pyproject.toml b/pyproject.toml index 1326a1ed..6d98eef2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling >=1.24", "hatch-vcs >=0.4"] build-backend = "hatchling.build" [project] @@ -77,6 +77,8 @@ hatch.scikit-build = "scikit_build_core.hatch.hooks" test = [ "build >=0.8", "cattrs >=22.2.0", + "hatchling >=1.24", + "hatch-vcs >=0.4", "pip>=23; python_version<'3.13'", "pip>=24.1; python_version>='3.13'", "pytest >=7.2", @@ -89,10 +91,6 @@ test = [ "virtualenv >=20.20", "wheel >=0.40", ] -test-hatchling = [ - { include-group = "test" }, - "hatchling >=1.24.0", -] test-meta = [ { include-group = "test" }, "hatch-fancy-pypi-readme>=22.3", @@ -120,7 +118,6 @@ cov = [ ] dev = [ { include-group = "cov" }, - { include-group = "test-hatchling" }, { include-group = "test-meta" }, { include-group = "test-numpy" }, { include-group = "test-pybind11" }, diff --git a/tests/conftest.py b/tests/conftest.py index 0c08eaa9..0e27009f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,6 +26,7 @@ from typing import TypeGuard +import download_wheels import pytest from packaging.requirements import Requirement from packaging.version import Version @@ -36,9 +37,11 @@ VIRTUALENV_VERSION = Version(metadata.version("virtualenv")) -@pytest.fixture(scope="session") -def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path: - wheelhouse = tmp_path_factory.mktemp("wheelhouse") +def pytest_configure(config) -> None: + if hasattr(config, "workerinput"): + return # Running in pytest-xdist worker + + wheelhouse = config.cache.mkdir("wheelhouse") subprocess.run( [ @@ -46,6 +49,7 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path: "-m", "pip", "wheel", + "--no-build-isolation", "--wheel-dir", str(wheelhouse), f"{BASE}", @@ -54,39 +58,9 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path: capture_output=True, text=True, ) - packages = [ - "build", - "cython", - "hatchling", - "pip", - "setuptools", - "virtualenv", - "wheel", - ] - if importlib.util.find_spec("cmake") is not None: - packages.append("cmake") - - if importlib.util.find_spec("ninja") is not None: - packages.append("ninja") - - if importlib.util.find_spec("pybind11") is not None: - packages.append("pybind11") - - subprocess.run( - [ - sys.executable, - "-m", - "pip", - "download", - "-q", - "-d", - str(wheelhouse), - *packages, - ], - check=True, - ) - return wheelhouse + if not all(list(wheelhouse.glob(f"{p}*.whl")) for p in download_wheels.WHEELS): + download_wheels.prepare(wheelhouse) class VEnv: @@ -180,9 +154,10 @@ def prepare_no_build_isolation(self) -> None: @pytest.fixture -def isolated(tmp_path: Path, pep518_wheelhouse: Path) -> VEnv: +def isolated(tmp_path: Path, pytestconfig: pytest.Config) -> VEnv: path = tmp_path / "venv" - return VEnv(path, wheelhouse=pep518_wheelhouse) + wheelhouse = pytestconfig.cache.mkdir("wheelhouse").resolve() + return VEnv(path, wheelhouse=wheelhouse) @pytest.fixture @@ -372,7 +347,7 @@ def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: item.add_marker(pytest.mark.network) -def pytest_report_header() -> str: +def pytest_report_header(config: pytest.Config) -> str: with BASE.joinpath("pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) project = pyproject.get("project", {}) @@ -384,6 +359,9 @@ def pytest_report_header() -> str: interesting_packages = {Requirement(p).name for p in pkgs} interesting_packages.add("pip") + wheelhouse = config.cache.mkdir("wheelhouse") + pkgs = wheelhouse.glob("*.whl") + valid = [] for package in sorted(interesting_packages): with contextlib.suppress(ModuleNotFoundError): @@ -392,5 +370,6 @@ def pytest_report_header() -> str: lines = [ f"installed packages of interest: {reqs}", f"sysconfig platform: {sysconfig.get_platform()}", + f"wheelhouse: {' '.join('-'.join(p.name.split('-')[:2]) for p in pkgs)}", ] return "\n".join(lines) diff --git a/tests/utils/download_wheels.py b/tests/utils/download_wheels.py new file mode 100644 index 00000000..13bca441 --- /dev/null +++ b/tests/utils/download_wheels.py @@ -0,0 +1,59 @@ +# /// script +# dependencies = ["pip"] +# /// + +""" +Download wheels into the pytest cache. Must be run from the pytest directory +(project root, usually). If run manually via a script runner, will always +include cmake/ninja. If run in an environment, requires pip. +""" + +import importlib.util +import subprocess +import sys +from pathlib import Path + +WHEELS = [ + "build", + "cython", + "hatchling", + "pip", + "pybind11", + "setuptools", + "virtualenv", + "wheel", +] + +if importlib.util.find_spec("cmake") is not None: + WHEELS.append("cmake") + +if importlib.util.find_spec("ninja") is not None: + WHEELS.append("ninja") + +if importlib.util.find_spec("pybind11") is not None: + WHEELS.append("pybind11") + + +def prepare(wheelhouse: Path) -> None: + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "download", + "-q", + "-d", + str(wheelhouse), + *WHEELS, + ], + check=True, + capture_output=True, + text=True, + ) + print(f"Downloaded wheels to {wheelhouse}") + + +if __name__ == "__main__": + wheelhouse = Path(".pytest_cache/d/wheelhouse") + wheelhouse.mkdir(parents=True, exist_ok=True) + prepare(wheelhouse) From 66af618214d90055b93f338278abdec9e51b094a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 22 Dec 2025 17:11:29 -0500 Subject: [PATCH 3/5] tests: remove capture (now triggered in different phase) Signed-off-by: Henry Schreiner --- .distro/python-scikit-build-core.spec | 1 + .github/workflows/ci.yml | 2 +- tests/conftest.py | 2 -- tests/utils/download_wheels.py | 6 ++---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.distro/python-scikit-build-core.spec b/.distro/python-scikit-build-core.spec index 852b2688..81408ba7 100644 --- a/.distro/python-scikit-build-core.spec +++ b/.distro/python-scikit-build-core.spec @@ -22,6 +22,7 @@ BuildRequires: ninja-build BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: git +BuildRequires: python3-hatch-vcs %global _description %{expand: A next generation Python CMake adapter and Python API for plugins diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41309ef0..2fa6fee7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,7 +223,7 @@ jobs: run: python3.13t -m venv /venv - name: Install deps - run: /venv/bin/pip install -e . --group=test ninja + run: /venv/bin/pip install -e . --group=test ninja pybind11 - name: Test package run: /venv/bin/pytest diff --git a/tests/conftest.py b/tests/conftest.py index 0e27009f..49ae5566 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,8 +55,6 @@ def pytest_configure(config) -> None: f"{BASE}", ], check=True, - capture_output=True, - text=True, ) if not all(list(wheelhouse.glob(f"{p}*.whl")) for p in download_wheels.WHEELS): diff --git a/tests/utils/download_wheels.py b/tests/utils/download_wheels.py index 13bca441..5114d4b9 100644 --- a/tests/utils/download_wheels.py +++ b/tests/utils/download_wheels.py @@ -4,8 +4,8 @@ """ Download wheels into the pytest cache. Must be run from the pytest directory -(project root, usually). If run manually via a script runner, will always -include cmake/ninja. If run in an environment, requires pip. +(project root, usually). If run in an environment, requires pip. Only downloads +pybind11, ninja, or cmake if those are in the environment already. """ import importlib.util @@ -47,8 +47,6 @@ def prepare(wheelhouse: Path) -> None: *WHEELS, ], check=True, - capture_output=True, - text=True, ) print(f"Downloaded wheels to {wheelhouse}") From 3fd2e3dc6bb4ff48d8a5c28678a8470207ee931b Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 23 Dec 2025 00:15:54 -0500 Subject: [PATCH 4/5] tests: use filelock instead of config Signed-off-by: Henry Schreiner --- .distro/python-scikit-build-core.spec | 1 - .github/workflows/ci.yml | 10 ++--- pyproject.toml | 1 + tests/conftest.py | 60 ++++++++++++++------------- tests/plans.fmf | 2 +- tests/test_hatchling.py | 4 +- tests/utils/download_wheels.py | 23 +++++----- 7 files changed, 54 insertions(+), 47 deletions(-) diff --git a/.distro/python-scikit-build-core.spec b/.distro/python-scikit-build-core.spec index 81408ba7..852b2688 100644 --- a/.distro/python-scikit-build-core.spec +++ b/.distro/python-scikit-build-core.spec @@ -22,7 +22,6 @@ BuildRequires: ninja-build BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: git -BuildRequires: python3-hatch-vcs %global _description %{expand: A next generation Python CMake adapter and Python API for plugins diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fa6fee7..e1ac1fa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,7 +187,7 @@ jobs: - name: Install min requirements run: | - uv pip install -e. --group=test --resolution=lowest-direct --system + uv pip install -e. --group=test-pybind11 --resolution=lowest-direct --system - name: Setup CMake 3.15 uses: jwlawson/actions-setup-cmake@v2.0 @@ -223,7 +223,7 @@ jobs: run: python3.13t -m venv /venv - name: Install deps - run: /venv/bin/pip install -e . --group=test ninja pybind11 + run: /venv/bin/pip install -e . --group=test-pybind11 ninja - name: Test package run: /venv/bin/pytest @@ -246,7 +246,7 @@ jobs: cmake ninja git make gcc-g++ python39 python39-devel python39-pip - name: Install - run: python3.9 -m pip install . --group=test + run: python3.9 -m pip install . --group=test-pybind11 - name: Test package run: @@ -279,7 +279,7 @@ jobs: persist-credentials: false - name: Install - run: python -m pip install . --group=test + run: python -m pip install . --group=test-pybind11 - name: Test package run: >- @@ -312,7 +312,7 @@ jobs: persist-credentials: false - name: Install - run: python -m pip install . --group=test + run: python -m pip install . --group=test-pybind11 - name: Test package run: >- diff --git a/pyproject.toml b/pyproject.toml index 6d98eef2..cdc3cbe4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,7 @@ hatch.scikit-build = "scikit_build_core.hatch.hooks" test = [ "build >=0.8", "cattrs >=22.2.0", + "filelock >=3.8.0", "hatchling >=1.24", "hatch-vcs >=0.4", "pip>=23; python_version<'3.13'", diff --git a/tests/conftest.py b/tests/conftest.py index 49ae5566..1d871f48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from typing import Any, Literal, overload import virtualenv as _virtualenv +from filelock import FileLock if sys.version_info < (3, 11): import tomli as tomllib @@ -37,28 +38,34 @@ VIRTUALENV_VERSION = Version(metadata.version("virtualenv")) -def pytest_configure(config) -> None: - if hasattr(config, "workerinput"): - return # Running in pytest-xdist worker - - wheelhouse = config.cache.mkdir("wheelhouse") - - subprocess.run( - [ - sys.executable, - "-m", - "pip", - "wheel", - "--no-build-isolation", - "--wheel-dir", - str(wheelhouse), - f"{BASE}", - ], - check=True, - ) +@pytest.fixture(scope="session") +def pep518_wheelhouse(pytestconfig: pytest.Config) -> Path: + wheelhouse = pytestconfig.cache.mkdir("wheelhouse") + + main_lock = FileLock(wheelhouse / "main.lock") + with main_lock: + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "wheel", + "--wheel-dir", + str(wheelhouse), + "--no-build-isolation", + f"{BASE}", + ], + check=True, + capture_output=True, + text=True, + ) - if not all(list(wheelhouse.glob(f"{p}*.whl")) for p in download_wheels.WHEELS): - download_wheels.prepare(wheelhouse) + wheels_lock = FileLock(wheelhouse / "wheels.lock") + with wheels_lock: + if not all(list(wheelhouse.glob(f"{p}*.whl")) for p in download_wheels.WHEELS): + download_wheels.prepare(wheelhouse) + + return wheelhouse class VEnv: @@ -152,10 +159,9 @@ def prepare_no_build_isolation(self) -> None: @pytest.fixture -def isolated(tmp_path: Path, pytestconfig: pytest.Config) -> VEnv: +def isolated(tmp_path: Path, pep518_wheelhouse: Path) -> VEnv: path = tmp_path / "venv" - wheelhouse = pytestconfig.cache.mkdir("wheelhouse").resolve() - return VEnv(path, wheelhouse=wheelhouse) + return VEnv(path, wheelhouse=pep518_wheelhouse) @pytest.fixture @@ -345,7 +351,7 @@ def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: item.add_marker(pytest.mark.network) -def pytest_report_header(config: pytest.Config) -> str: +def pytest_report_header() -> str: with BASE.joinpath("pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) project = pyproject.get("project", {}) @@ -357,9 +363,6 @@ def pytest_report_header(config: pytest.Config) -> str: interesting_packages = {Requirement(p).name for p in pkgs} interesting_packages.add("pip") - wheelhouse = config.cache.mkdir("wheelhouse") - pkgs = wheelhouse.glob("*.whl") - valid = [] for package in sorted(interesting_packages): with contextlib.suppress(ModuleNotFoundError): @@ -368,6 +371,5 @@ def pytest_report_header(config: pytest.Config) -> str: lines = [ f"installed packages of interest: {reqs}", f"sysconfig platform: {sysconfig.get_platform()}", - f"wheelhouse: {' '.join('-'.join(p.name.split('-')[:2]) for p in pkgs)}", ] return "\n".join(lines) diff --git a/tests/plans.fmf b/tests/plans.fmf index 6a12e7fb..4189e4d7 100644 --- a/tests/plans.fmf +++ b/tests/plans.fmf @@ -27,7 +27,7 @@ node-date: 2025-02-27T16:18:39-05:00 describe-name: v0.11.0 EOF - pip install --user . --group=test + pip install --user . --group=test-pybind11 discover: how: fmf filter: "tag: pytest" diff --git a/tests/test_hatchling.py b/tests/test_hatchling.py index 8fc19344..3e5f3330 100644 --- a/tests/test_hatchling.py +++ b/tests/test_hatchling.py @@ -5,6 +5,8 @@ import pytest +import download_wheels + pytest.importorskip("hatchling") @@ -42,7 +44,7 @@ def test_hatchling_sdist(isolated, tmp_path: Path) -> None: ) def test_hatchling_wheel(isolated, build_args, tmp_path: Path) -> None: dist = tmp_path / "dist" - isolated.install("build[virtualenv]", "scikit-build-core", "hatchling", "pybind11") + isolated.install("build[virtualenv]", "scikit-build-core", "hatchling", *download_wheels.EXTRA) isolated.module("build", "--no-isolation", f"--outdir={dist}", *build_args) ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") diff --git a/tests/utils/download_wheels.py b/tests/utils/download_wheels.py index 5114d4b9..5c85f0d2 100644 --- a/tests/utils/download_wheels.py +++ b/tests/utils/download_wheels.py @@ -13,26 +13,29 @@ import sys from pathlib import Path + +EXTRA = [] + +if importlib.util.find_spec("cmake") is not None: + EXTRA.append("cmake") + +if importlib.util.find_spec("ninja") is not None: + EXTRA.append("ninja") + +if importlib.util.find_spec("pybind11") is not None: + EXTRA.append("pybind11") + WHEELS = [ "build", "cython", "hatchling", "pip", - "pybind11", "setuptools", "virtualenv", "wheel", + *EXTRA, ] -if importlib.util.find_spec("cmake") is not None: - WHEELS.append("cmake") - -if importlib.util.find_spec("ninja") is not None: - WHEELS.append("ninja") - -if importlib.util.find_spec("pybind11") is not None: - WHEELS.append("pybind11") - def prepare(wheelhouse: Path) -> None: subprocess.run( From 536e2acb8e11a92aeb8bdba3c8bf79f8c1411a0d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:40:09 +0000 Subject: [PATCH 5/5] style: pre-commit fixes --- tests/test_hatchling.py | 7 ++++--- tests/utils/download_wheels.py | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_hatchling.py b/tests/test_hatchling.py index 3e5f3330..8e832b3a 100644 --- a/tests/test_hatchling.py +++ b/tests/test_hatchling.py @@ -3,9 +3,8 @@ import zipfile from pathlib import Path -import pytest - import download_wheels +import pytest pytest.importorskip("hatchling") @@ -44,7 +43,9 @@ def test_hatchling_sdist(isolated, tmp_path: Path) -> None: ) def test_hatchling_wheel(isolated, build_args, tmp_path: Path) -> None: dist = tmp_path / "dist" - isolated.install("build[virtualenv]", "scikit-build-core", "hatchling", *download_wheels.EXTRA) + isolated.install( + "build[virtualenv]", "scikit-build-core", "hatchling", *download_wheels.EXTRA + ) isolated.module("build", "--no-isolation", f"--outdir={dist}", *build_args) ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") diff --git a/tests/utils/download_wheels.py b/tests/utils/download_wheels.py index 5c85f0d2..29c4b44e 100644 --- a/tests/utils/download_wheels.py +++ b/tests/utils/download_wheels.py @@ -13,7 +13,6 @@ import sys from pathlib import Path - EXTRA = [] if importlib.util.find_spec("cmake") is not None: