Skip to content

Commit 24cb523

Browse files
authored
feat: respect metadata_directory (#487)
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
1 parent cd95f0f commit 24cb523

File tree

4 files changed

+86
-43
lines changed

4 files changed

+86
-43
lines changed

src/poetry/core/masonry/api.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,8 @@ def prepare_metadata_for_build_wheel(
3939
) -> str:
4040
poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
4141
builder = WheelBuilder(poetry)
42-
43-
dist_info = Path(metadata_directory, builder.dist_info)
44-
dist_info.mkdir(parents=True, exist_ok=True)
45-
46-
if "scripts" in poetry.local_config or "plugins" in poetry.local_config:
47-
with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f:
48-
builder._write_entry_points(f)
49-
50-
with (dist_info / "WHEEL").open("w", encoding="utf-8") as f:
51-
builder._write_wheel_file(f)
52-
53-
with (dist_info / "METADATA").open("w", encoding="utf-8") as f:
54-
builder._write_metadata_file(f)
55-
42+
metadata_path = Path(metadata_directory)
43+
dist_info = builder.prepare_metadata(metadata_path)
5644
return dist_info.name
5745

5846

@@ -63,8 +51,11 @@ def build_wheel(
6351
) -> str:
6452
"""Builds a wheel, places it in wheel_directory"""
6553
poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
54+
metadata_path = None if metadata_directory is None else Path(metadata_directory)
6655

67-
return WheelBuilder.make_in(poetry, Path(wheel_directory))
56+
return WheelBuilder.make_in(
57+
poetry, Path(wheel_directory), metadata_directory=metadata_path
58+
)
6859

6960

7061
def build_sdist(

src/poetry/core/masonry/builders/wheel.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from poetry.core.masonry.utils.helpers import distribution_name
2828
from poetry.core.masonry.utils.helpers import normalize_file_permissions
2929
from poetry.core.masonry.utils.package_include import PackageInclude
30+
from poetry.core.utils.helpers import temporary_directory
3031

3132

3233
if TYPE_CHECKING:
@@ -53,6 +54,7 @@ def __init__(
5354
original: Path | None = None,
5455
executable: Path | None = None,
5556
editable: bool = False,
57+
metadata_directory: Path | None = None,
5658
) -> None:
5759
super().__init__(poetry, executable=executable)
5860

@@ -61,6 +63,7 @@ def __init__(
6163
if original:
6264
self._original_path = original.parent
6365
self._editable = editable
66+
self._metadata_directory = metadata_directory
6467

6568
@classmethod
6669
def make_in(
@@ -70,12 +73,14 @@ def make_in(
7073
original: Path | None = None,
7174
executable: Path | None = None,
7275
editable: bool = False,
76+
metadata_directory: Path | None = None,
7377
) -> str:
7478
wb = WheelBuilder(
7579
poetry,
7680
original=original,
7781
executable=executable,
7882
editable=editable,
83+
metadata_directory=metadata_directory,
7984
)
8085
wb.build(target_dir=directory)
8186

@@ -105,19 +110,25 @@ def build(
105110
with os.fdopen(fd, "w+b") as fd_file, zipfile.ZipFile(
106111
fd_file, mode="w", compression=zipfile.ZIP_DEFLATED
107112
) as zip_file:
108-
if not self._editable:
109-
if not self._poetry.package.build_should_generate_setup():
110-
self._build(zip_file)
111-
self._copy_module(zip_file)
112-
else:
113-
self._copy_module(zip_file)
114-
self._build(zip_file)
115-
else:
113+
if self._editable:
116114
self._build(zip_file)
117115
self._add_pth(zip_file)
116+
elif self._poetry.package.build_should_generate_setup():
117+
self._copy_module(zip_file)
118+
self._build(zip_file)
119+
else:
120+
self._build(zip_file)
121+
self._copy_module(zip_file)
118122

119123
self._copy_file_scripts(zip_file)
120-
self._write_metadata(zip_file)
124+
125+
if self._metadata_directory is None:
126+
with temporary_directory() as temp_dir:
127+
metadata_directory = self.prepare_metadata(Path(temp_dir))
128+
self._copy_dist_info(zip_file, metadata_directory)
129+
else:
130+
self._copy_dist_info(zip_file, self._metadata_directory)
131+
121132
self._write_record(zip_file)
122133

123134
wheel_path = target_dir / self.wheel_filename
@@ -225,33 +236,42 @@ def _copy_module(self, wheel: zipfile.ZipFile) -> None:
225236
for file in sorted(to_add, key=lambda x: x.path):
226237
self._add_file(wheel, file.path, file.relative_to_source_root())
227238

228-
def _write_metadata(self, wheel: zipfile.ZipFile) -> None:
239+
def prepare_metadata(self, metadata_directory: Path) -> Path:
240+
dist_info = metadata_directory / self.dist_info
241+
dist_info.mkdir(parents=True, exist_ok=True)
242+
229243
if (
230244
"scripts" in self._poetry.local_config
231245
or "plugins" in self._poetry.local_config
232246
):
233-
with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f:
247+
with (dist_info / "entry_points.txt").open(
248+
"w", encoding="utf-8", newline="\n"
249+
) as f:
234250
self._write_entry_points(f)
235251

236-
license_files_to_add = []
252+
with (dist_info / "WHEEL").open("w", encoding="utf-8", newline="\n") as f:
253+
self._write_wheel_file(f)
254+
255+
with (dist_info / "METADATA").open("w", encoding="utf-8", newline="\n") as f:
256+
self._write_metadata_file(f)
257+
258+
license_files = set()
237259
for base in ("COPYING", "LICENSE"):
238-
license_files_to_add.append(self._path / base)
239-
license_files_to_add.extend(self._path.glob(base + ".*"))
260+
license_files.add(self._path / base)
261+
license_files.update(self._path.glob(base + ".*"))
240262

241-
license_files_to_add.extend(self._path.joinpath("LICENSES").glob("**/*"))
263+
license_files.update(self._path.joinpath("LICENSES").glob("**/*"))
242264

243-
for path in set(license_files_to_add):
244-
if path.is_file():
245-
relative_path = f"{self.dist_info}/{path.relative_to(self._path)}"
246-
self._add_file(wheel, path, relative_path)
247-
else:
248-
logger.debug(f"Skipping: {path.as_posix()}")
265+
for license_file in license_files:
266+
if not license_file.is_file():
267+
logger.debug(f"Skipping: {license_file.as_posix()}")
268+
continue
249269

250-
with self._write_to_zip(wheel, self.dist_info + "/WHEEL") as f:
251-
self._write_wheel_file(f)
270+
dest = dist_info / license_file.relative_to(self._path)
271+
os.makedirs(dest.parent, exist_ok=True)
272+
shutil.copy(license_file, dest)
252273

253-
with self._write_to_zip(wheel, self.dist_info + "/METADATA") as f:
254-
self._write_metadata_file(f)
274+
return dist_info
255275

256276
def _write_record(self, wheel: zipfile.ZipFile) -> None:
257277
# Write a record of the files in the wheel
@@ -272,6 +292,16 @@ def _write_record(self, wheel: zipfile.ZipFile) -> None:
272292

273293
f.write(record.getvalue())
274294

295+
def _copy_dist_info(self, wheel: zipfile.ZipFile, source: Path) -> None:
296+
dist_info = Path(self.dist_info)
297+
for file in source.glob("**/*"):
298+
if not file.is_file():
299+
continue
300+
301+
rel_path = file.relative_to(source)
302+
target = dist_info / rel_path
303+
self._add_file(wheel, file, target)
304+
275305
@property
276306
def dist_info(self) -> str:
277307
return self.dist_info_name(self._package.name, self._meta.version)

tests/masonry/builders/test_wheel.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,20 @@ def test_dist_info_file_permissions() -> None:
180180

181181
with zipfile.ZipFile(str(whl)) as z:
182182
assert (
183-
z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr == 0o644 << 16
183+
z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr & 0x1FF0000
184+
== 0o644 << 16
184185
)
185186
assert (
186-
z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr
187+
z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr & 0x1FF0000
187188
== 0o644 << 16
188189
)
189190
assert (
190-
z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr == 0o644 << 16
191+
z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr & 0x1FF0000
192+
== 0o644 << 16
191193
)
192194
assert (
193195
z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr
196+
& 0x1FF0000
194197
== 0o644 << 16
195198
)
196199

tests/masonry/test_api.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,22 @@ def test_build_editable_wheel() -> None:
235235

236236
assert "my_package.pth" in namelist
237237
assert pkg_dir.as_posix() == z.read("my_package.pth").decode().strip()
238+
239+
240+
def test_build_wheel_with_metadata_directory() -> None:
241+
with temporary_directory() as metadata_tmp_dir, cwd(
242+
os.path.join(fixtures, "complete")
243+
):
244+
metadata_directory = api.prepare_metadata_for_build_wheel(metadata_tmp_dir)
245+
246+
with temporary_directory() as wheel_tmp_dir:
247+
dist_info_path = Path(metadata_tmp_dir) / metadata_directory
248+
filename = api.build_wheel(
249+
wheel_tmp_dir, metadata_directory=str(dist_info_path)
250+
)
251+
validate_wheel_contents(
252+
name="my_package",
253+
version="1.2.3",
254+
path=str(os.path.join(wheel_tmp_dir, filename)),
255+
files=["entry_points.txt"],
256+
)

0 commit comments

Comments
 (0)