diff --git a/src/poetry/core/masonry/api.py b/src/poetry/core/masonry/api.py index 2ed4ed5f3..4cd7aefc0 100644 --- a/src/poetry/core/masonry/api.py +++ b/src/poetry/core/masonry/api.py @@ -39,20 +39,8 @@ def prepare_metadata_for_build_wheel( ) -> str: poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) builder = WheelBuilder(poetry) - - dist_info = Path(metadata_directory, builder.dist_info) - dist_info.mkdir(parents=True, exist_ok=True) - - if "scripts" in poetry.local_config or "plugins" in poetry.local_config: - with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f: - builder._write_entry_points(f) - - with (dist_info / "WHEEL").open("w", encoding="utf-8") as f: - builder._write_wheel_file(f) - - with (dist_info / "METADATA").open("w", encoding="utf-8") as f: - builder._write_metadata_file(f) - + metadata_path = Path(metadata_directory) + dist_info = builder.prepare_metadata(metadata_path) return dist_info.name @@ -63,8 +51,11 @@ def build_wheel( ) -> str: """Builds a wheel, places it in wheel_directory""" poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) + metadata_path = None if metadata_directory is None else Path(metadata_directory) - return WheelBuilder.make_in(poetry, Path(wheel_directory)) + return WheelBuilder.make_in( + poetry, Path(wheel_directory), metadata_directory=metadata_path + ) def build_sdist( diff --git a/src/poetry/core/masonry/builders/wheel.py b/src/poetry/core/masonry/builders/wheel.py index 2909cff74..ba0f4ed69 100644 --- a/src/poetry/core/masonry/builders/wheel.py +++ b/src/poetry/core/masonry/builders/wheel.py @@ -27,6 +27,7 @@ from poetry.core.masonry.utils.helpers import distribution_name from poetry.core.masonry.utils.helpers import normalize_file_permissions from poetry.core.masonry.utils.package_include import PackageInclude +from poetry.core.utils.helpers import temporary_directory if TYPE_CHECKING: @@ -53,6 +54,7 @@ def __init__( original: Path | None = None, executable: Path | None = None, editable: bool = False, + metadata_directory: Path | None = None, ) -> None: super().__init__(poetry, executable=executable) @@ -61,6 +63,7 @@ def __init__( if original: self._original_path = original.parent self._editable = editable + self._metadata_directory = metadata_directory @classmethod def make_in( @@ -70,12 +73,14 @@ def make_in( original: Path | None = None, executable: Path | None = None, editable: bool = False, + metadata_directory: Path | None = None, ) -> str: wb = WheelBuilder( poetry, original=original, executable=executable, editable=editable, + metadata_directory=metadata_directory, ) wb.build(target_dir=directory) @@ -105,19 +110,25 @@ def build( with os.fdopen(fd, "w+b") as fd_file, zipfile.ZipFile( fd_file, mode="w", compression=zipfile.ZIP_DEFLATED ) as zip_file: - if not self._editable: - if not self._poetry.package.build_should_generate_setup(): - self._build(zip_file) - self._copy_module(zip_file) - else: - self._copy_module(zip_file) - self._build(zip_file) - else: + if self._editable: self._build(zip_file) self._add_pth(zip_file) + elif self._poetry.package.build_should_generate_setup(): + self._copy_module(zip_file) + self._build(zip_file) + else: + self._build(zip_file) + self._copy_module(zip_file) self._copy_file_scripts(zip_file) - self._write_metadata(zip_file) + + if self._metadata_directory is None: + with temporary_directory() as temp_dir: + metadata_directory = self.prepare_metadata(Path(temp_dir)) + self._copy_dist_info(zip_file, metadata_directory) + else: + self._copy_dist_info(zip_file, self._metadata_directory) + self._write_record(zip_file) wheel_path = target_dir / self.wheel_filename @@ -225,33 +236,42 @@ def _copy_module(self, wheel: zipfile.ZipFile) -> None: for file in sorted(to_add, key=lambda x: x.path): self._add_file(wheel, file.path, file.relative_to_source_root()) - def _write_metadata(self, wheel: zipfile.ZipFile) -> None: + def prepare_metadata(self, metadata_directory: Path) -> Path: + dist_info = metadata_directory / self.dist_info + dist_info.mkdir(parents=True, exist_ok=True) + if ( "scripts" in self._poetry.local_config or "plugins" in self._poetry.local_config ): - with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f: + with (dist_info / "entry_points.txt").open( + "w", encoding="utf-8", newline="\n" + ) as f: self._write_entry_points(f) - license_files_to_add = [] + with (dist_info / "WHEEL").open("w", encoding="utf-8", newline="\n") as f: + self._write_wheel_file(f) + + with (dist_info / "METADATA").open("w", encoding="utf-8", newline="\n") as f: + self._write_metadata_file(f) + + license_files = set() for base in ("COPYING", "LICENSE"): - license_files_to_add.append(self._path / base) - license_files_to_add.extend(self._path.glob(base + ".*")) + license_files.add(self._path / base) + license_files.update(self._path.glob(base + ".*")) - license_files_to_add.extend(self._path.joinpath("LICENSES").glob("**/*")) + license_files.update(self._path.joinpath("LICENSES").glob("**/*")) - for path in set(license_files_to_add): - if path.is_file(): - relative_path = f"{self.dist_info}/{path.relative_to(self._path)}" - self._add_file(wheel, path, relative_path) - else: - logger.debug(f"Skipping: {path.as_posix()}") + for license_file in license_files: + if not license_file.is_file(): + logger.debug(f"Skipping: {license_file.as_posix()}") + continue - with self._write_to_zip(wheel, self.dist_info + "/WHEEL") as f: - self._write_wheel_file(f) + dest = dist_info / license_file.relative_to(self._path) + os.makedirs(dest.parent, exist_ok=True) + shutil.copy(license_file, dest) - with self._write_to_zip(wheel, self.dist_info + "/METADATA") as f: - self._write_metadata_file(f) + return dist_info def _write_record(self, wheel: zipfile.ZipFile) -> None: # Write a record of the files in the wheel @@ -272,6 +292,16 @@ def _write_record(self, wheel: zipfile.ZipFile) -> None: f.write(record.getvalue()) + def _copy_dist_info(self, wheel: zipfile.ZipFile, source: Path) -> None: + dist_info = Path(self.dist_info) + for file in source.glob("**/*"): + if not file.is_file(): + continue + + rel_path = file.relative_to(source) + target = dist_info / rel_path + self._add_file(wheel, file, target) + @property def dist_info(self) -> str: return self.dist_info_name(self._package.name, self._meta.version) diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 3bc15234c..2a9ee05a0 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -180,17 +180,20 @@ def test_dist_info_file_permissions() -> None: with zipfile.ZipFile(str(whl)) as z: assert ( - z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr == 0o644 << 16 + z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr & 0x1FF0000 + == 0o644 << 16 ) assert ( - z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr + z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr & 0x1FF0000 == 0o644 << 16 ) assert ( - z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr == 0o644 << 16 + z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr & 0x1FF0000 + == 0o644 << 16 ) assert ( z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr + & 0x1FF0000 == 0o644 << 16 ) diff --git a/tests/masonry/test_api.py b/tests/masonry/test_api.py index 4b8c6feec..dfc1699c3 100644 --- a/tests/masonry/test_api.py +++ b/tests/masonry/test_api.py @@ -235,3 +235,22 @@ def test_build_editable_wheel() -> None: assert "my_package.pth" in namelist assert pkg_dir.as_posix() == z.read("my_package.pth").decode().strip() + + +def test_build_wheel_with_metadata_directory() -> None: + with temporary_directory() as metadata_tmp_dir, cwd( + os.path.join(fixtures, "complete") + ): + metadata_directory = api.prepare_metadata_for_build_wheel(metadata_tmp_dir) + + with temporary_directory() as wheel_tmp_dir: + dist_info_path = Path(metadata_tmp_dir) / metadata_directory + filename = api.build_wheel( + wheel_tmp_dir, metadata_directory=str(dist_info_path) + ) + validate_wheel_contents( + name="my_package", + version="1.2.3", + path=str(os.path.join(wheel_tmp_dir, filename)), + files=["entry_points.txt"], + )