diff --git a/noxfile.py b/noxfile.py index a123a01..1f4906b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,13 +1,13 @@ """ Tests for the py-build-cmake package. - - Build all example projects + - Build all example and test projects - Run the package's pytest tests - Check the contents of the sdist and Wheel packages produced - Build the component backend example - Run the package's pytest tests - Check the contents of the Wheel packages produced - - Test all three editable modes for the pybind11-project example + - Test all three (+1) editable modes for the example and test projects - Install in editable mode and run the package's pytest - Run the py-build-cmake pytest tests """ @@ -185,30 +185,52 @@ def component(session: nox.Session): session.run("pytest") -def test_editable(session: nox.Session, mode: str): +def test_editable( + session: nox.Session, name: str, mode: str, dir: Path = Path("examples") +): tmpdir = Path(session.create_tmp()).resolve() + m = mode.split("+", 1) + bh = str(len(m) > 1 and m[1] == "build_hook").lower() + skip_wrapper = ("namespace", "bare", "cmake-preset") + if m[0] == "wrapper" and any(k in name for k in skip_wrapper): + return try: - with session.chdir("examples/pybind11-project"): + with session.chdir(dir / name): shutil.rmtree(".py-build-cmake_cache", ignore_errors=True) with (tmpdir / f"{mode}.toml").open("w") as f: - f.write(f'[editable]\nmode = "{mode}"') - session.install("-e", ".", "--config-settings=--local=" + f.name) + f.write(f'[editable]\nmode = "{m[0]}"\nbuild_hook = {bh}') + args = ("--config-settings=--local=" + f.name,) + if bh: + args += ("--no-build-isolation",) + session.install("-e", ".", *args) session.run("pytest") finally: shutil.rmtree(tmpdir, ignore_errors=True) @nox.session -def editable(session: nox.Session): - session.install("-U", "pip", "build", "pytest") +@nox.parametrize("mode", ["symlink", "symlink+build_hook", "hook", "wrapper"]) +def editable(session: nox.Session, mode): + session.install( + "-U", + "pip", + "build", + "pytest", + "pybind11~=2.10.1", + "pybind11-stubgen~=0.16.2", + "nanobind~=1.5.1", + "nanobind-stubgen~=0.1.1", + ) dist_dir = os.getenv("PY_BUILD_CMAKE_WHEEL_DIR") if dist_dir is None: session.run("python", "-m", "build", ".") dist_dir = "dist" session.env["PIP_FIND_LINKS"] = str(Path(dist_dir).resolve()) session.install(f"py-build-cmake=={version}") - for mode in "wrapper", "hook", "symlink": - test_editable(session, mode) + for name in examples: + test_editable(session, name, mode) + for name in test_packages: + test_editable(session, name, mode, dir=Path("test-packages")) @nox.session diff --git a/src/py_build_cmake/build.py b/src/py_build_cmake/build.py index d0561f1..d79e3e7 100644 --- a/src/py_build_cmake/build.py +++ b/src/py_build_cmake/build.py @@ -205,11 +205,10 @@ def build_wheel_in_dir( ) # Copy the module's Python source files to the temporary folder - if not module.is_generated: - if not editable: - export_util.copy_pkg_source_to(paths.staging_dir, module) - else: - paths = export_editable.do_editable_install(cfg, paths, module) + if not editable: + export_util.copy_pkg_source_to(paths.staging_dir, module) + else: + paths = export_editable.do_editable_install(cfg, paths, module) # Create dist-info folder distinfo_dir = f"{pkg_info.norm_name}-{pkg_info.version}.dist-info" diff --git a/src/py_build_cmake/common/__init__.py b/src/py_build_cmake/common/__init__.py index ef56f84..4bf1f4f 100644 --- a/src/py_build_cmake/common/__init__.py +++ b/src/py_build_cmake/common/__init__.py @@ -68,6 +68,10 @@ def iter_files_abs(self): """ assert self.is_package or not self.is_namespace + if self.is_generated: + # Generated modules/packages don't exist in the source directory + return + def _include(s: str | Path): p = Path(s) return p.name != "__pycache__" and not p.name.endswith(".pyc") @@ -115,6 +119,11 @@ def check(self): ): msg = "Namespace packages cannot use editable mode 'wrapper'" raise ConfigError(msg) + if self.module.get("generated") and any( + e["mode"] == "wrapper" for e in self.editable.values() + ): + msg = "Generated modules/packages cannot use editable mode 'wrapper'" + raise ConfigError(msg) @dataclass diff --git a/src/py_build_cmake/config/options/pyproject_options.py b/src/py_build_cmake/config/options/pyproject_options.py index 0b9e120..ffbab80 100644 --- a/src/py_build_cmake/config/options/pyproject_options.py +++ b/src/py_build_cmake/config/options/pyproject_options.py @@ -63,7 +63,7 @@ def get_options(project_path: Path, *, test: bool = False): "Set to true for PEP 420 namespace packages.", default=DefaultValueValue(False)), EnumConfigOption("generated", - "Do not try to locate the module in the source " + "Do not try to locate the main module in the source " "directory, but assume that it is generated by CMake. " "Dynamic metadata cannot be used when set.", options=["module", "package"]), diff --git a/src/py_build_cmake/export/editable/build_hook.py b/src/py_build_cmake/export/editable/build_hook.py index 3d9707b..53c466b 100644 --- a/src/py_build_cmake/export/editable/build_hook.py +++ b/src/py_build_cmake/export/editable/build_hook.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import textwrap from pathlib import Path @@ -7,12 +8,17 @@ from ...common import Config, Module from ...common.util import get_os_name +logger = logging.getLogger(__name__) + def write_build_hook(cfg: Config, staging_dir: Path, module: Module, cmaker: CMaker): """Write a hook that re-compiles extension modules.""" edit_cfg = cfg.editable["cross" if cfg.cross else get_os_name()] if not edit_cfg.get("build_hook"): return + if edit_cfg.get("mode") != "symlink": + logger.warning("Skipping build_hook: only supported for symlink mode") + return name = module.name pkg_hook = staging_dir / (name + "_build_hook") pkg_hook.mkdir(parents=True, exist_ok=True) diff --git a/src/py_build_cmake/export/editable/hook.py b/src/py_build_cmake/export/editable/hook.py index 021398e..dd32d73 100644 --- a/src/py_build_cmake/export/editable/hook.py +++ b/src/py_build_cmake/export/editable/hook.py @@ -22,7 +22,9 @@ def __init__(self, name, extra_path): def find_spec(self, name, path=None, target=None): if name.split('.', 1)[0] != self.name: return None - path = (path or []) + [self.extra_path] + if path is None: + path = [] + path.append(self.extra_path) return super().find_spec(name, path, target) def install(name: str): diff --git a/src/py_build_cmake/export/sdist.py b/src/py_build_cmake/export/sdist.py index 969cc30..809aa07 100644 --- a/src/py_build_cmake/export/sdist.py +++ b/src/py_build_cmake/export/sdist.py @@ -152,8 +152,7 @@ def select_files(self): include tests, docs, etc. for a 'gold standard' sdist. """ make_rel = lambda p: p.relative_to(self.module.base_path) - if not self.module.is_generated: - yield from map(make_rel, self.module.iter_files_abs()) + yield from map(make_rel, self.module.iter_files_abs()) yield from map(make_rel, self.extra_files) def crucial_files(self):