Skip to content

Commit

Permalink
feat: respect metadata_directory (#487)
Browse files Browse the repository at this point in the history
From [PEP 517]:

> If the build frontend has previously called prepare_metadata_for_build_wheel
> and depends on the wheel resulting from this call to have metadata matching
> this earlier call, then it should provide the path to the created .dist-info
> directory as the metadata_directory argument. If this argument is provided,
> then build_wheel MUST produce a wheel with identical metadata. The directory
> passed in by the build frontend MUST be identical to the directory created by
> prepare_metadata_for_build_wheel, including any unrecognized files it created.

  [PEP 517]: https://peps.python.org/pep-0517/#build-wheel
  • Loading branch information
dimbleby committed Nov 18, 2022
1 parent cd95f0f commit 24cb523
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 43 deletions.
21 changes: 6 additions & 15 deletions src/poetry/core/masonry/api.py
Expand Up @@ -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


Expand All @@ -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(
Expand Down
80 changes: 55 additions & 25 deletions src/poetry/core/masonry/builders/wheel.py
Expand Up @@ -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:
Expand All @@ -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)

Expand All @@ -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(
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions tests/masonry/builders/test_wheel.py
Expand Up @@ -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
)

Expand Down
19 changes: 19 additions & 0 deletions tests/masonry/test_api.py
Expand Up @@ -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"],
)

0 comments on commit 24cb523

Please sign in to comment.