diff --git a/src/cpp/tests/wheel_smoke.py b/src/cpp/tests/wheel_smoke.py index c81cf5da..a9ed4882 100644 --- a/src/cpp/tests/wheel_smoke.py +++ b/src/cpp/tests/wheel_smoke.py @@ -21,7 +21,8 @@ def main() -> None: # The extension is the one shipped in the wheel, and it carries its own OpenCASCADE (no OCP, no system OCCT). here = Path(cpp.__file__).resolve().parent - assert any(here.glob("_native*")), f"no compiled extension next to {cpp.__file__}" + # a file: a directory `_native/` next to it holds the type stubs and is not the extension + assert any(path.is_file() for path in here.glob("_native*")), f"no compiled extension next to {cpp.__file__}" from simplecadapi._cpp import occt print("python", sys.version.split()[0], "| OpenCASCADE", occt.__version__, "|", here) diff --git a/test/test_source_tree_is_built.py b/test/test_source_tree_is_built.py new file mode 100644 index 00000000..0a4f6318 --- /dev/null +++ b/test/test_source_tree_is_built.py @@ -0,0 +1,41 @@ +"""A checkout is a built source tree only when the compiled module is there; the type stubs do not count. + +The checkout carries `src/simplecadapi/_cpp/_native/*.pyi` (a directory). The validation workers put the checkout's +source on the path of their subprocess only when it is built, because otherwise it shadows the installed wheel with Python +files that have no compiled module: the wheel test job of the 2.2.7 dry run failed that way. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools" / "research" / "build123dftc")) +from b3ftc.validation import _source_tree_is_built # noqa: E402 + + +def make(tmp_path: Path, *names: str, stubs: bool = False) -> Path: + cpp = tmp_path / "simplecadapi" / "_cpp" + cpp.mkdir(parents=True) + if stubs: + (cpp / "_native").mkdir() + (cpp / "_native" / "__init__.pyi").write_text("") + for name in names: + (cpp / name).write_bytes(b"") + return tmp_path + + +def test_the_type_stub_directory_alone_is_not_a_build(tmp_path) -> None: + assert not _source_tree_is_built(make(tmp_path, stubs=True)) + + +def test_nothing_is_not_a_build(tmp_path) -> None: + assert not _source_tree_is_built(make(tmp_path)) + + +@pytest.mark.parametrize("name", ["_native.cpython-312-darwin.so", "_native.cpython-313-x86_64-linux-gnu.so", "_native.cp312-win_amd64.pyd"]) +def test_the_compiled_module_is_a_build_with_or_without_stubs(tmp_path, name) -> None: + assert _source_tree_is_built(make(tmp_path, name, stubs=True)) + diff --git a/tools/research/build123dftc/b3ftc/validation.py b/tools/research/build123dftc/b3ftc/validation.py index 70bbf727..0c0ae925 100644 --- a/tools/research/build123dftc/b3ftc/validation.py +++ b/tools/research/build123dftc/b3ftc/validation.py @@ -159,6 +159,16 @@ def _interpreter(executable: str | None) -> str: return os.path.abspath(executable) if os.sep in executable else executable +def _source_tree_is_built(source: Path) -> bool: + """Whether the compiled module sits next to the Python files of *source* (a development build). + + The module is a file (`_native*.so` / `_native*.pyd`); a *directory* `_native/` next to it holds the type stubs a + checkout carries and says nothing about a build. + """ + + return any(path.is_file() for path in (source / "simplecadapi" / "_cpp").glob("_native*")) + + def _process(command: list[str], staging: Path, env: Mapping[str, str], output: Path, timeout: float) -> dict[str, Any]: if timeout <= 0: @@ -222,7 +232,7 @@ def _worker(ftc_path: Path, workdir: Path | None, timeout: float, # The checkout's source tree goes on the path only when it is a built one (the compiled module sits next to # its Python files, as in a development build); otherwise it would shadow the installed wheel. source = repo_root / "src" - built = any((source / "simplecadapi" / "_cpp").glob("_native*")) + built = _source_tree_is_built(source) env["PYTHONPATH"] = os.pathsep.join( [*([str(source)] if built else []), str(tool_dir), env.get("PYTHONPATH", "")]) started = time.monotonic() diff --git a/tools/research/cadqueryftc/cadquery_validate.py b/tools/research/cadqueryftc/cadquery_validate.py index a613aa63..b9e88757 100644 --- a/tools/research/cadqueryftc/cadquery_validate.py +++ b/tools/research/cadqueryftc/cadquery_validate.py @@ -32,7 +32,10 @@ if str(_TOOLS_DIR) not in sys.path: sys.path.insert(0, str(_TOOLS_DIR)) # the checkout's source tree only when it is a built one (it would shadow an installed wheel otherwise) -if str(_REPO_ROOT / "src") not in sys.path and any((_REPO_ROOT / "src" / "simplecadapi" / "_cpp").glob("_native*")): +# (the module is a file; a directory `_native/` next to it holds the type stubs and is no sign of a build) +if str(_REPO_ROOT / "src") not in sys.path and any( + path.is_file() for path in (_REPO_ROOT / "src" / "simplecadapi" / "_cpp").glob("_native*") +): sys.path.insert(0, str(_REPO_ROOT / "src")) from cqftc.tracer.trace_schema import OperationTrace # noqa: E402