Skip to content

Commit bdb88c0

Browse files
committed
Improve atomicity when writing PKG-INFO
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.
1 parent 5ed19c7 commit bdb88c0

File tree

2 files changed

+24
-1
lines changed

2 files changed

+24
-1
lines changed

setuptools/_core_metadata.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
44
See: https://packaging.python.org/en/latest/specifications/core-metadata/
55
"""
6+
import os
7+
import stat
68
import textwrap
79
from email import message_from_file
810
from email.message import Message
11+
from tempfile import NamedTemporaryFile
912
from typing import Optional, List
1013

1114
from distutils.util import rfc822_escape
@@ -119,6 +122,24 @@ def single_line(val):
119122
return val
120123

121124

125+
def write_pkg_info(self, base_dir):
126+
"""Write the PKG-INFO file into the release tree."""
127+
temp = ""
128+
final = os.path.join(base_dir, 'PKG-INFO')
129+
try:
130+
# Use a temporary file while writing to avoid race conditions
131+
# (e.g. `importlib.metadata` reading `.egg-info/PKG-INFO`):
132+
with NamedTemporaryFile("w", encoding="utf-8", dir=base_dir, delete=False) as f:
133+
temp = f.name
134+
self.write_pkg_file(f)
135+
permissions = stat.S_IMODE(os.lstat(temp).st_mode)
136+
os.chmod(temp, permissions | stat.S_IRGRP | stat.S_IROTH)
137+
os.replace(temp, final) # atomic operation.
138+
finally:
139+
if temp and os.path.exists(temp):
140+
os.remove(temp)
141+
142+
122143
# Based on Python 3.5 version
123144
def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
124145
"""Write the PKG-INFO format data to a file object."""

setuptools/monkey.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def _patch_distribution_metadata():
102102
from . import _core_metadata
103103

104104
"""Patch write_pkg_file and read_pkg_file for higher metadata standards"""
105-
for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
105+
for attr in (
106+
'write_pkg_info', 'write_pkg_file', 'read_pkg_file', 'get_metadata_version'
107+
):
106108
new_val = getattr(setuptools._core_metadata, attr)
107109
setattr(distutils.dist.DistributionMetadata, attr, new_val)
108110

0 commit comments

Comments
 (0)