diff --git a/README.md b/README.md index 478bb3a2..532afd85 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,9 @@ wheel.cmake = true wheel.platlib = "" # A set of patterns to exclude from the wheel. This is additive to the SDist -# exclude patterns. This applies to the source files, not the final paths. -# Editable installs may not respect this exclusion. +# exclude patterns. This applies to the final paths in the wheel, and can +# exclude files from CMake output as well. Editable installs may not respect +# this exclusion. wheel.exclude = [] # The build tag to use for the wheel. If empty, no build tag is used. diff --git a/docs/configuration.md b/docs/configuration.md index e47c6de8..3e27cc12 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -269,6 +269,13 @@ as well (not guaranteed to be respected by editable installs): wheel.exclude = ["**.pyx"] ``` +:::{versionchanged} 0.9 + +Before scikit-build-core 0.9, these were matched on the source path, rather than +the wheel path, and didn't apply to CMake output. + +::: + :::{note} There are two more settings that are primarily intended for `overrides` (see diff --git a/pyproject.toml b/pyproject.toml index 4e244da0..63cdcdf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ test = [ "build[virtualenv]", "cattrs >=22.2.0", "pathspec >=0.10.1", + "pip", "pybind11", "pyproject-metadata >=0.5", "pytest >=7.0", # 7.2+ recommended for better tracebacks with ExceptionGroup diff --git a/src/scikit_build_core/build/_pathutil.py b/src/scikit_build_core/build/_pathutil.py index 107511f5..f013f807 100644 --- a/src/scikit_build_core/build/_pathutil.py +++ b/src/scikit_build_core/build/_pathutil.py @@ -4,6 +4,8 @@ from pathlib import Path from typing import TYPE_CHECKING +import pathspec + from ._file_processor import each_unignored_file if TYPE_CHECKING: @@ -37,20 +39,23 @@ def packages_to_file_mapping( packages: Sequence[str], platlib_dir: Path, include: Sequence[str], - exclude: Sequence[str], + src_exclude: Sequence[str], + target_exclude: Sequence[str], ) -> dict[str, str]: mapping = {} + exclude_spec = pathspec.GitIgnoreSpec.from_lines(target_exclude) for package in packages: source_package = Path(package) base_path = source_package.parent for filepath in each_unignored_file( source_package, include=include, - exclude=exclude, + exclude=src_exclude, ): - package_dir = platlib_dir / filepath.relative_to(base_path) - if not package_dir.is_file(): - mapping[str(filepath)] = str(package_dir) + rel_path = filepath.relative_to(base_path) + target_path = platlib_dir / rel_path + if not exclude_spec.match_file(rel_path) and not target_path.is_file(): + mapping[str(filepath)] = str(target_path) return mapping diff --git a/src/scikit_build_core/build/_wheelfile.py b/src/scikit_build_core/build/_wheelfile.py index 516e70d7..b3ceb803 100644 --- a/src/scikit_build_core/build/_wheelfile.py +++ b/src/scikit_build_core/build/_wheelfile.py @@ -17,11 +17,12 @@ from zipfile import ZipInfo import packaging.utils +import pathspec from .. import __version__ if TYPE_CHECKING: - from collections.abc import Mapping, Set + from collections.abc import Mapping, Sequence, Set from packaging.tags import Tag from pyproject_metadata import StandardMetadata @@ -141,7 +142,9 @@ def dist_info_contents(self) -> dict[str, bytes]: **license_entries, } - def build(self, wheel_dirs: dict[str, Path]) -> None: + def build( + self, wheel_dirs: Mapping[str, Path], exclude: Sequence[str] = () + ) -> None: (targetlib,) = {"platlib", "purelib"} & set(wheel_dirs) assert {targetlib, "data", "headers", "scripts", "null"} >= wheel_dirs.keys() @@ -152,14 +155,21 @@ def build(self, wheel_dirs: dict[str, Path]) -> None: for key in sorted({"data", "headers", "scripts"} & wheel_dirs.keys()): plans[key] = wheel_dirs[key] + exclude_spec = pathspec.GitIgnoreSpec.from_lines(exclude) + for key, path in plans.items(): for filename in sorted(path.glob("**/*")): - is_in_dist_info = any(x.endswith(".dist-info") for x in filename.parts) - is_python_cache = filename.suffix in {".pyc", ".pyo"} - if filename.is_file() and not is_in_dist_info and not is_python_cache: - relpath = filename.relative_to(path) - target = Path(data_dir) / key / relpath if key else relpath - self.write(str(filename), str(target)) + if not filename.is_file(): + continue + if any(x.endswith(".dist-info") for x in filename.parts): + continue + if filename.suffix in {".pyc", ".pyo"}: + continue + relpath = filename.relative_to(path) + if exclude_spec.match_file(relpath): + continue + target = Path(data_dir) / key / relpath if key else relpath + self.write(str(filename), str(target)) dist_info_contents = self.dist_info_contents() for key, data in dist_info_contents.items(): diff --git a/src/scikit_build_core/build/wheel.py b/src/scikit_build_core/build/wheel.py index 974d71c4..5f3bcdf9 100644 --- a/src/scikit_build_core/build/wheel.py +++ b/src/scikit_build_core/build/wheel.py @@ -344,7 +344,8 @@ def _build_wheel_impl( packages=packages, platlib_dir=wheel_dirs[targetlib], include=settings.sdist.include, - exclude=[*settings.sdist.exclude, *settings.wheel.exclude], + src_exclude=settings.sdist.exclude, + target_exclude=settings.wheel.exclude, ) if not editable: @@ -364,7 +365,7 @@ def _build_wheel_impl( ), license_files=license_files, ) as wheel: - wheel.build(wheel_dirs) + wheel.build(wheel_dirs, exclude=settings.wheel.exclude) str_pkgs = (str(Path.cwd().joinpath(p).parent.resolve()) for p in packages) if editable and settings.editable.mode == "redirect": diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index c02871e5..cb8ca2c1 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -209,7 +209,7 @@ "items": { "type": "string" }, - "description": "A set of patterns to exclude from the wheel. This is additive to the SDist exclude patterns. This applies to the source files, not the final paths. Editable installs may not respect this exclusion." + "description": "A set of patterns to exclude from the wheel. This is additive to the SDist exclude patterns. This applies to the final paths in the wheel, and can exclude files from CMake output as well. Editable installs may not respect this exclusion." }, "build-tag": { "type": "string", diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index 9cc413ad..9258e145 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -193,8 +193,9 @@ class WheelSettings: exclude: List[str] = dataclasses.field(default_factory=list) """ A set of patterns to exclude from the wheel. This is additive to the SDist - exclude patterns. This applies to the source files, not the final paths. - Editable installs may not respect this exclusion. + exclude patterns. This applies to the final paths in the wheel, and can + exclude files from CMake output as well. Editable installs may not respect + this exclusion. """ build_tag: str = "" diff --git a/tests/packages/simplest_c/CMakeLists.txt b/tests/packages/simplest_c/CMakeLists.txt index 61105f9e..f610e446 100644 --- a/tests/packages/simplest_c/CMakeLists.txt +++ b/tests/packages/simplest_c/CMakeLists.txt @@ -34,3 +34,7 @@ install( file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated_ignored.txt "Testing") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/generated_ignored.txt DESTINATION ${SKBUILD_PROJECT_NAME}) + +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated_no_wheel.txt "Testing") +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/generated_no_wheel.txt + DESTINATION ${SKBUILD_PROJECT_NAME}) diff --git a/tests/test_editable_unit.py b/tests/test_editable_unit.py index 3dcc4524..a0451f14 100644 --- a/tests/test_editable_unit.py +++ b/tests/test_editable_unit.py @@ -139,7 +139,8 @@ def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VE packages=packages, platlib_dir=site_packages, include=[], - exclude=[], + src_exclude=[], + target_exclude=[], ) assert mapping == { str(Path("pkg/__init__.py")): str(pkg_dir / "__init__.py"), diff --git a/tests/test_simplest_c.py b/tests/test_simplest_c.py index c2961ca8..cf6af489 100644 --- a/tests/test_simplest_c.py +++ b/tests/test_simplest_c.py @@ -82,6 +82,7 @@ def test_pep517_wheel(tmp_path, monkeypatch, virtualenv, component): if not component: expected_wheel_files.add("generated_ignored.txt") + expected_wheel_files.add("generated_no_wheel.txt") if not component or "Generated" in component: expected_wheel_files.add("generated.txt") @@ -110,7 +111,10 @@ def test_pep517_wheel_incexl(tmp_path, monkeypatch, virtualenv): { "sdist.include": "src/simplest/*included*.txt", "sdist.exclude": "src/simplest/*excluded*.txt", - "wheel.exclude": "src/simplest/sdist_only.txt", + "wheel.exclude": [ + "simplest/sdist_only.txt", + "simplest/generated_no_wheel.txt", + ], "wheel.packages": ["src/simplest", "src/not_a_package"], }, )