Skip to content

Commit bbe070e

Browse files
committed
wip: PEP 660 backend support
currently the contents for the {name}.pth file is hardcoded for the psf/black project (because of course :P)
1 parent 8af23a4 commit bbe070e

File tree

1 file changed

+110
-24
lines changed

1 file changed

+110
-24
lines changed

setuptools/build_meta.py

Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,16 @@
3434
import contextlib
3535
import tempfile
3636
import warnings
37+
import zipfile
38+
import base64
39+
import textwrap
40+
import hashlib
41+
import csv
3742

3843
import setuptools
3944
import distutils
4045

46+
import pkg_resources
4147
from pkg_resources import parse_requirements
4248

4349
__all__ = ['get_requires_for_build_sdist',
@@ -126,6 +132,27 @@ def suppress_known_deprecation():
126132
yield
127133

128134

135+
def _urlsafe_b64encode(data):
136+
"""urlsafe_b64encode without padding"""
137+
return base64.urlsafe_b64encode(data).rstrip(b"=")
138+
139+
140+
def _add_wheel_record(archive, dist_info):
141+
"""
142+
Add the wheel RECORD manifest.
143+
"""
144+
buffer = io.StringIO()
145+
writer = csv.writer(buffer, delimiter=',', quotechar='"', lineterminator='\n')
146+
for f in archive.namelist():
147+
data = archive.read(f)
148+
size = len(data)
149+
digest = hashlib.sha256(data).digest()
150+
digest = "sha256=" + (_urlsafe_b64encode(digest).decode("ascii"))
151+
writer.writerow((f, digest, size))
152+
record_path = os.path.join(dist_info, "RECORD")
153+
archive.writestr(zipfile.ZipInfo(record_path), buffer.read())
154+
155+
129156
class _BuildMetaBackend(object):
130157

131158
def _fix_config(self, config_settings):
@@ -146,6 +173,29 @@ def _get_build_requires(self, config_settings, requirements):
146173

147174
return requirements
148175

176+
def _build_dist_info_metadata(self, result_directory):
177+
sys.argv = sys.argv[:1] + [
178+
'dist_info', '--egg-base', result_directory]
179+
with no_install_setup_requires():
180+
self.run_setup()
181+
182+
dist_info_directory = result_directory
183+
while True:
184+
dist_infos = [f for f in os.listdir(dist_info_directory)
185+
if f.endswith('.dist-info')]
186+
187+
if (
188+
len(dist_infos) == 0 and
189+
len(_get_immediate_subdirectories(dist_info_directory)) == 1
190+
):
191+
192+
dist_info_directory = os.path.join(
193+
dist_info_directory, os.listdir(dist_info_directory)[0])
194+
continue
195+
196+
assert len(dist_infos) == 1
197+
return dist_infos[0], dist_info_directory
198+
149199
def run_setup(self, setup_script='setup.py'):
150200
# Note that we can reuse our build directory between calls
151201
# Correctness comes first, then optimization later
@@ -166,39 +216,23 @@ def get_requires_for_build_sdist(self, config_settings=None):
166216
config_settings = self._fix_config(config_settings)
167217
return self._get_build_requires(config_settings, requirements=[])
168218

219+
def get_requires_for_build_editable(self, config_settings=None):
220+
config_settings = self._fix_config(config_settings)
221+
return self._get_build_requires(config_settings, requirements=[])
222+
169223
def prepare_metadata_for_build_wheel(self, metadata_directory,
170224
config_settings=None):
171-
sys.argv = sys.argv[:1] + [
172-
'dist_info', '--egg-base', metadata_directory]
173-
with no_install_setup_requires():
174-
self.run_setup()
175-
176-
dist_info_directory = metadata_directory
177-
while True:
178-
dist_infos = [f for f in os.listdir(dist_info_directory)
179-
if f.endswith('.dist-info')]
180-
181-
if (
182-
len(dist_infos) == 0 and
183-
len(_get_immediate_subdirectories(dist_info_directory)) == 1
184-
):
185-
186-
dist_info_directory = os.path.join(
187-
dist_info_directory, os.listdir(dist_info_directory)[0])
188-
continue
189-
190-
assert len(dist_infos) == 1
191-
break
192-
225+
dist_info, dist_info_directory = \
226+
self._build_dist_info_metadata(metadata_directory)
193227
# PEP 517 requires that the .dist-info directory be placed in the
194228
# metadata_directory. To comply, we MUST copy the directory to the root
195229
if dist_info_directory != metadata_directory:
196230
shutil.move(
197-
os.path.join(dist_info_directory, dist_infos[0]),
231+
os.path.join(dist_info_directory, dist_info),
198232
metadata_directory)
199233
shutil.rmtree(dist_info_directory, ignore_errors=True)
200234

201-
return dist_infos[0]
235+
return dist_info
202236

203237
def _build_with_temp_dir(self, setup_command, result_extension,
204238
result_directory, config_settings):
@@ -235,6 +269,55 @@ def build_sdist(self, sdist_directory, config_settings=None):
235269
'.tar.gz', sdist_directory,
236270
config_settings)
237271

272+
def build_editable(self, wheel_directory, config_settings=None,
273+
metadata_directory=None):
274+
config_settings = self._fix_config(config_settings)
275+
# TODO: using wheel_directory like this is probably a bad idea, fix it
276+
dist_info, dist_info_directory = self._build_dist_info_metadata(wheel_directory)
277+
dist_info_path = os.path.join(dist_info_directory, dist_info)
278+
279+
sys.argv = [
280+
*sys.argv[:1], 'build_ext', '--inplace',
281+
*config_settings['--global-option']
282+
]
283+
with no_install_setup_requires():
284+
self.run_setup()
285+
# TODO: this is super sketchy, it's worth a cleanup
286+
provider = pkg_resources.PathMetadata(dist_info_path, dist_info_path)
287+
dist = pkg_resources.DistInfoDistribution.from_filename(
288+
dist_info_path, metadata=provider
289+
)
290+
wheel_name = f"{dist.project_name}-{dist.version}-ed.py3-none-any.whl"
291+
wheel_path = os.path.join(wheel_directory, wheel_name)
292+
293+
with zipfile.ZipFile(
294+
wheel_path, "a", compression=zipfile.ZIP_DEFLATED
295+
) as archive:
296+
# TODO: don't hardcode this obviously *somehow*
297+
data = "/home/ichard26/programming/oss/black/src"
298+
archive.writestr(zipfile.ZipInfo(f'{dist.project_name}.pth'), data)
299+
300+
for file in sorted(os.listdir(dist_info_path)):
301+
file = os.path.join(dist_info_path, file)
302+
with open(file, "r", encoding="utf-8") as metadata:
303+
zip_filename = os.path.relpath(file, dist_info_directory)
304+
archive.writestr(
305+
zipfile.ZipInfo(zip_filename), metadata.read()
306+
)
307+
308+
archive.writestr(
309+
zipfile.ZipInfo(os.path.join(dist_info, "WHEEL")),
310+
textwrap.dedent(f"""\
311+
Wheel-Version: 1.0
312+
Generator: setuptools ({setuptools.__version__})
313+
Root-Is-Purelib: false
314+
Tag: ed.py3-none-any
315+
""")
316+
)
317+
_add_wheel_record(archive, dist_info)
318+
319+
return os.path.basename(wheel_path)
320+
238321

239322
class _BuildMetaLegacyBackend(_BuildMetaBackend):
240323
"""Compatibility backend for setuptools
@@ -281,9 +364,12 @@ def run_setup(self, setup_script='setup.py'):
281364

282365
get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
283366
get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
367+
get_requires_for_build_editable = _BACKEND.get_requires_for_build_editable
284368
prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
369+
prepare_metadata_for_build_editable = _BACKEND.prepare_metadata_for_build_wheel
285370
build_wheel = _BACKEND.build_wheel
286371
build_sdist = _BACKEND.build_sdist
372+
build_editable = _BACKEND.build_editable
287373

288374

289375
# The legacy backend

0 commit comments

Comments
 (0)