diff --git a/backend/src/hatchling/builders/wheel.py b/backend/src/hatchling/builders/wheel.py index 711d6e81f..1b1440742 100644 --- a/backend/src/hatchling/builders/wheel.py +++ b/backend/src/hatchling/builders/wheel.py @@ -170,17 +170,13 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: @cached_property def default_file_selection_options(self) -> FileSelectionOptions: - if include := self.target_config.get('include', self.build_config.get('include', [])): - return FileSelectionOptions(include, [], [], []) + include = self.target_config.get('include', self.build_config.get('include', [])) + exclude = self.target_config.get('exclude', self.build_config.get('exclude', [])) + packages = self.target_config.get('packages', self.build_config.get('packages', [])) + only_include = self.target_config.get('only-include', self.build_config.get('only-include', [])) - if exclude := self.target_config.get('exclude', self.build_config.get('exclude', [])): - return FileSelectionOptions([], exclude, [], []) - - if packages := self.target_config.get('packages', self.build_config.get('packages', [])): - return FileSelectionOptions([], [], packages, []) - - if only_include := self.target_config.get('only-include', self.build_config.get('only-include', [])): - return FileSelectionOptions([], [], [], only_include) + if include or packages or only_include: + return FileSelectionOptions(include, exclude, packages, only_include) for project_name in ( self.builder.normalize_file_name_component(self.builder.metadata.core.raw_name), @@ -188,16 +184,16 @@ def default_file_selection_options(self) -> FileSelectionOptions: ): if os.path.isfile(os.path.join(self.root, project_name, '__init__.py')): normalized_project_name = self.get_raw_fs_path_name(self.root, project_name) - return FileSelectionOptions([], [], [normalized_project_name], []) + return FileSelectionOptions([], exclude, [normalized_project_name], []) if os.path.isfile(os.path.join(self.root, 'src', project_name, '__init__.py')): normalized_project_name = self.get_raw_fs_path_name(os.path.join(self.root, 'src'), project_name) - return FileSelectionOptions([], [], [f'src/{normalized_project_name}'], []) + return FileSelectionOptions([], exclude, [f'src/{normalized_project_name}'], []) module_file = f'{project_name}.py' if os.path.isfile(os.path.join(self.root, module_file)): normalized_project_name = self.get_raw_fs_path_name(self.root, module_file) - return FileSelectionOptions([], [], [], [module_file]) + return FileSelectionOptions([], exclude, [], [module_file]) from glob import glob @@ -205,11 +201,11 @@ def default_file_selection_options(self) -> FileSelectionOptions: if len(possible_namespace_packages) == 1: relative_path = os.path.relpath(possible_namespace_packages[0], self.root) namespace = relative_path.split(os.sep)[0] - return FileSelectionOptions([], [], [namespace], []) + return FileSelectionOptions([], exclude, [namespace], []) if self.build_artifact_spec is not None or self.get_force_include(): self.set_exclude_all() - return FileSelectionOptions([], [], [], []) + return FileSelectionOptions([], exclude, [], []) message = ( 'Unable to determine which files to ship inside the wheel using the following heuristics: ' diff --git a/docs/history/hatchling.md b/docs/history/hatchling.md index 238f2decd..077f8bb76 100644 --- a/docs/history/hatchling.md +++ b/docs/history/hatchling.md @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +***Fixed:*** + +- Fix regression in 1.19.1 that allowed `exclude` to count toward inclusion selection, thus bypassing the default inclusion selection heuristics + ## [1.19.1](https://github.com/pypa/hatch/releases/tag/hatchling-v1.19.1) - 2023-12-12 ## {: #hatchling-v1.19.1 } ***Fixed:*** diff --git a/tests/backend/builders/test_wheel.py b/tests/backend/builders/test_wheel.py index ecd6682c0..8eb9d470f 100644 --- a/tests/backend/builders/test_wheel.py +++ b/tests/backend/builders/test_wheel.py @@ -34,8 +34,36 @@ def test_default_versions(isolation): class TestDefaultFileSelection: + def test_already_defined(self, temp_dir): + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': { + 'hatch': { + 'build': { + 'targets': { + 'wheel': { + 'include': ['foo'], + 'exclude': ['bar'], + 'packages': ['foo', 'bar', 'baz'], + 'only-include': ['baz'], + } + } + } + } + }, + } + builder = WheelBuilder(str(temp_dir), config=config) + + assert builder.config.default_include() == ['foo'] + assert builder.config.default_exclude() == ['bar'] + assert builder.config.default_packages() == ['foo', 'bar', 'baz'] + assert builder.config.default_only_include() == ['baz'] + def test_flat_layout(self, temp_dir): - config = {'project': {'name': 'my-app', 'version': '0.0.1'}} + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': {'hatch': {'build': {'targets': {'wheel': {'exclude': ['foobarbaz']}}}}}, + } builder = WheelBuilder(str(temp_dir), config=config) flat_root = temp_dir / 'my_app' / '__init__.py' @@ -54,12 +82,15 @@ def test_flat_layout(self, temp_dir): namespace_root.touch() assert builder.config.default_include() == [] - assert builder.config.default_exclude() == [] + assert builder.config.default_exclude() == ['foobarbaz'] assert builder.config.default_packages() == ['my_app'] assert builder.config.default_only_include() == [] def test_src_layout(self, temp_dir): - config = {'project': {'name': 'my-app', 'version': '0.0.1'}} + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': {'hatch': {'build': {'targets': {'wheel': {'exclude': ['foobarbaz']}}}}}, + } builder = WheelBuilder(str(temp_dir), config=config) src_root = temp_dir / 'src' / 'my_app' / '__init__.py' @@ -74,12 +105,15 @@ def test_src_layout(self, temp_dir): namespace_root.touch() assert builder.config.default_include() == [] - assert builder.config.default_exclude() == [] + assert builder.config.default_exclude() == ['foobarbaz'] assert builder.config.default_packages() == ['src/my_app'] assert builder.config.default_only_include() == [] def test_single_module(self, temp_dir): - config = {'project': {'name': 'my-app', 'version': '0.0.1'}} + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': {'hatch': {'build': {'targets': {'wheel': {'exclude': ['foobarbaz']}}}}}, + } builder = WheelBuilder(str(temp_dir), config=config) single_module_root = temp_dir / 'my_app.py' @@ -90,12 +124,15 @@ def test_single_module(self, temp_dir): namespace_root.touch() assert builder.config.default_include() == [] - assert builder.config.default_exclude() == [] + assert builder.config.default_exclude() == ['foobarbaz'] assert builder.config.default_packages() == [] assert builder.config.default_only_include() == ['my_app.py'] def test_namespace(self, temp_dir): - config = {'project': {'name': 'my-app', 'version': '0.0.1'}} + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': {'hatch': {'build': {'targets': {'wheel': {'exclude': ['foobarbaz']}}}}}, + } builder = WheelBuilder(str(temp_dir), config=config) namespace_root = temp_dir / 'ns' / 'my_app' / '__init__.py' @@ -103,12 +140,15 @@ def test_namespace(self, temp_dir): namespace_root.touch() assert builder.config.default_include() == [] - assert builder.config.default_exclude() == [] + assert builder.config.default_exclude() == ['foobarbaz'] assert builder.config.default_packages() == ['ns'] assert builder.config.default_only_include() == [] def test_default_error(self, temp_dir): - config = {'project': {'name': 'my-app', 'version': '0.0.1'}} + config = { + 'project': {'name': 'my-app', 'version': '0.0.1'}, + 'tool': {'hatch': {'build': {'targets': {'wheel': {'exclude': ['foobarbaz']}}}}}, + } builder = WheelBuilder(str(temp_dir), config=config) for method in (