Skip to content

Commit

Permalink
Improve atomicity when writing PKG-INFO
Browse files Browse the repository at this point in the history
For the time being, when `setuptools.build_meta` is called,
`egg_info.egg_base` is accidentally set to the project root between the
several calls to the different build hooks.

This means that if the hooks are called, they will try to overwrite
`setuptools.egg-info/PKG-INFO`, and for a very short interval of time it
will be an empty file.

Another process may then try to simultaneously use `importlib.metadata`
to list entry-points. However to sort entry-points, `importlib.metadata`
will try to read the `Name` field in the `PKG-INFO/METADATA` files and
will raise an error/warning if that file is empty.

This commit tries to use `os.replace` to avoid having an empty PKG-INFO
while `importlib.metadata` is used.
  • Loading branch information
abravalheri committed May 3, 2023
1 parent 81498f1 commit ae8ec5c
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 1 deletion.
21 changes: 21 additions & 0 deletions setuptools/_core_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
See: https://packaging.python.org/en/latest/specifications/core-metadata/
"""
import os
import stat
import textwrap
from email import message_from_file
from email.message import Message
from tempfile import NamedTemporaryFile
from typing import Optional, List

from distutils.util import rfc822_escape
Expand Down Expand Up @@ -122,6 +125,24 @@ def single_line(val):
return val


def write_pkg_info(self, base_dir):
"""Write the PKG-INFO file into the release tree."""
temp = ""
final = os.path.join(base_dir, 'PKG-INFO')
try:
# Use a temporary file while writing to avoid race conditions
# (e.g. `importlib.metadata` reading `.egg-info/PKG-INFO`):
with NamedTemporaryFile("w", encoding="utf-8", dir=base_dir, delete=False) as f:
temp = f.name
self.write_pkg_file(f)
permissions = stat.S_IMODE(os.lstat(temp).st_mode)
os.chmod(temp, permissions | stat.S_IRGRP | stat.S_IROTH)
os.replace(temp, final) # atomic operation.
finally:
if temp and os.path.exists(temp):
os.remove(temp)


# Based on Python 3.5 version
def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
"""Write the PKG-INFO format data to a file object."""
Expand Down
4 changes: 3 additions & 1 deletion setuptools/monkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def patch_all():

def _patch_distribution_metadata():
"""Patch write_pkg_file and read_pkg_file for higher metadata standards"""
for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
for attr in (
'write_pkg_info', 'write_pkg_file', 'read_pkg_file', 'get_metadata_version'
):
new_val = getattr(setuptools._core_metadata, attr)
setattr(distutils.dist.DistributionMetadata, attr, new_val)

Expand Down

0 comments on commit ae8ec5c

Please sign in to comment.