diff --git a/backend/src/hatchling/builders/wheel.py b/backend/src/hatchling/builders/wheel.py index 4f4d09a32..88ccb8054 100644 --- a/backend/src/hatchling/builders/wheel.py +++ b/backend/src/hatchling/builders/wheel.py @@ -4,6 +4,7 @@ import hashlib import os import stat +import sys import tempfile import zipfile from io import StringIO @@ -178,15 +179,19 @@ def set_default_file_selection(self) -> None: self.builder.normalize_file_name_component(self.builder.metadata.core.name), ): if os.path.isfile(os.path.join(self.root, project_name, '__init__.py')): - self.__packages.append(project_name) + normalized_project_name = self.get_raw_fs_path_name(self.root, project_name) + self.__packages.append(normalized_project_name) break if os.path.isfile(os.path.join(self.root, 'src', project_name, '__init__.py')): - self.__packages.append(f'src/{project_name}') + normalized_project_name = self.get_raw_fs_path_name(os.path.join(self.root, 'src'), project_name) + self.__packages.append(f'src/{normalized_project_name}') break - if os.path.isfile(os.path.join(self.root, f'{project_name}.py')): - self.__only_include.append(f'{project_name}.py') + 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) + self.__only_include.append(module_file) break from glob import glob @@ -344,6 +349,24 @@ def macos_max_compat(self) -> bool: return self.__macos_max_compat + if sys.platform in {'darwin', 'win32'}: + + @staticmethod + def get_raw_fs_path_name(directory: str, name: str) -> str: + normalized = name.casefold() + entries = os.listdir(directory) + for entry in entries: + if entry.casefold() == normalized: + return entry + + return name # no cov + + else: + + @staticmethod + def get_raw_fs_path_name(directory: str, name: str) -> str: # noqa: ARG004 + return name + class WheelBuilder(BuilderInterface): """ diff --git a/docs/history/hatchling.md b/docs/history/hatchling.md index b1c635a42..fdc8a4f79 100644 --- a/docs/history/hatchling.md +++ b/docs/history/hatchling.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fix parsing dependencies for environments when warnings are emitted - Properly handle non-zero version epoch for the `standard` version scheme - Allow using an empty string for the `sources` option to add a prefix to distribution paths +- Fix the `wheel` target for case insensitive file systems when the project metadata name does not match the directory name on disk ## [1.18.0](https://github.com/pypa/hatch/releases/tag/hatchling-v1.18.0) - 2023-06-12 ## {: #hatchling-v1.18.0 } diff --git a/tests/backend/builders/test_wheel.py b/tests/backend/builders/test_wheel.py index dfe2be1a3..6990d28c7 100644 --- a/tests/backend/builders/test_wheel.py +++ b/tests/backend/builders/test_wheel.py @@ -121,7 +121,7 @@ def test_default(self, temp_dir): assert builder.config.default_packages() == [] assert builder.config.default_only_include() == [] - def test_raw_name_not_normalized(self, temp_dir): + def test_unnormalized_name_with_unnormalized_directory(self, temp_dir): config = {'project': {'name': 'MyApp', 'version': '0.0.1'}} builder = WheelBuilder(str(temp_dir), config=config) @@ -131,6 +131,16 @@ def test_raw_name_not_normalized(self, temp_dir): assert builder.config.default_packages() == ['src/MyApp'] + def test_unnormalized_name_with_normalized_directory(self, temp_dir): + config = {'project': {'name': 'MyApp', 'version': '0.0.1'}} + builder = WheelBuilder(str(temp_dir), config=config) + + src_root = temp_dir / 'src' / 'myapp' / '__init__.py' + src_root.ensure_parent_dir_exists() + src_root.touch() + + assert builder.config.default_packages() == ['src/myapp'] + class TestCoreMetadataConstructor: def test_default(self, isolation):