diff --git a/MANIFEST.in b/MANIFEST.in index aa3ab2b92..d15e2cccb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ # installed wheel silently omitted them while source-tree CI stayed green); graft # removes that failure mode at the root. graft solstone +include build_hooks.py # Exclude only compiled/editor cruft. global-exclude *.py[cod] diff --git a/build_hooks.py b/build_hooks.py new file mode 100644 index 000000000..4c9ed40c7 --- /dev/null +++ b/build_hooks.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Setuptools build hooks for solstone packaging.""" + +from __future__ import annotations + +import shutil +from pathlib import Path + +from setuptools.command.build_py import build_py + + +class BuildPyWithoutTests(build_py): + """Build the base wheel without in-package test directories.""" + + def run(self) -> None: + super().run() + solstone_root = Path(self.build_lib) / "solstone" + if not solstone_root.exists(): + return + for tests_dir in sorted(solstone_root.rglob("tests"), reverse=True): + if tests_dir.is_dir(): + shutil.rmtree(tests_dir) diff --git a/pyproject.toml b/pyproject.toml index a29a8f057..91d3ad57c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,6 +202,10 @@ include-package-data = true [tool.setuptools.packages.find] include = ["solstone*"] +exclude = ["*.tests", "*.tests.*"] + +[tool.setuptools.cmdclass] +build_py = "build_hooks.BuildPyWithoutTests" [tool.uv] # The [journal]/[journal-cuda] tombstones pin solstone-journal-host==0.7.0, a diff --git a/scripts/check_wheel_contents.py b/scripts/check_wheel_contents.py index 9adb6c4a1..72a438f80 100644 --- a/scripts/check_wheel_contents.py +++ b/scripts/check_wheel_contents.py @@ -106,6 +106,10 @@ def check_base_wheel(path: Path, max_bytes: int) -> list[str]: onnx_members = _onnx_members(path) if onnx_members: errors.append(f"{path.name}: base wheel contains ONNX members: {onnx_members}") + with zipfile.ZipFile(path) as wheel: + for member in wheel.namelist(): + if "tests" in member.split("/"): + errors.append(f"{path.name}: base wheel ships test path {member}") return errors diff --git a/tests/test_check_wheel_contents.py b/tests/test_check_wheel_contents.py index 37a227d27..c97db35a8 100644 --- a/tests/test_check_wheel_contents.py +++ b/tests/test_check_wheel_contents.py @@ -117,6 +117,25 @@ def test_core_wheel_validator_rejects_record_drift(tmp_path: Path) -> None: assert any("RECORD hash mismatch" in error for error in errors) +def test_base_wheel_validator_rejects_tests_path_segment(tmp_path: Path) -> None: + clean = _write_minimal_wheel(tmp_path, "solstone") + + clean_errors = checker.check_base_wheel(clean, checker.MAX_BASE_WHEEL_BYTES) + assert not [ + error for error in clean_errors if "base wheel ships test path" in error + ] + + dirty = _write_minimal_wheel(tmp_path, "solstone_dirty") + test_member = "solstone/apps/search/tests/test_routes.py" + with zipfile.ZipFile(dirty, "a") as wheel: + _write_member(wheel, test_member, b"") + + dirty_errors = checker.check_base_wheel(dirty, checker.MAX_BASE_WHEEL_BYTES) + assert any( + f"base wheel ships test path {test_member}" in error for error in dirty_errors + ) + + def _add_tar_member(archive: tarfile.TarFile, name: str) -> None: content = b"x" info = tarfile.TarInfo(name)