34
34
import contextlib
35
35
import tempfile
36
36
import warnings
37
+ import zipfile
38
+ import base64
39
+ import textwrap
40
+ import hashlib
41
+ import csv
37
42
38
43
import setuptools
39
44
import distutils
40
45
46
+ import pkg_resources
41
47
from pkg_resources import parse_requirements
42
48
43
49
__all__ = ['get_requires_for_build_sdist' ,
@@ -126,6 +132,27 @@ def suppress_known_deprecation():
126
132
yield
127
133
128
134
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
+
129
156
class _BuildMetaBackend (object ):
130
157
131
158
def _fix_config (self , config_settings ):
@@ -146,6 +173,29 @@ def _get_build_requires(self, config_settings, requirements):
146
173
147
174
return requirements
148
175
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
+
149
199
def run_setup (self , setup_script = 'setup.py' ):
150
200
# Note that we can reuse our build directory between calls
151
201
# Correctness comes first, then optimization later
@@ -166,39 +216,23 @@ def get_requires_for_build_sdist(self, config_settings=None):
166
216
config_settings = self ._fix_config (config_settings )
167
217
return self ._get_build_requires (config_settings , requirements = [])
168
218
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
+
169
223
def prepare_metadata_for_build_wheel (self , metadata_directory ,
170
224
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 )
193
227
# PEP 517 requires that the .dist-info directory be placed in the
194
228
# metadata_directory. To comply, we MUST copy the directory to the root
195
229
if dist_info_directory != metadata_directory :
196
230
shutil .move (
197
- os .path .join (dist_info_directory , dist_infos [ 0 ] ),
231
+ os .path .join (dist_info_directory , dist_info ),
198
232
metadata_directory )
199
233
shutil .rmtree (dist_info_directory , ignore_errors = True )
200
234
201
- return dist_infos [ 0 ]
235
+ return dist_info
202
236
203
237
def _build_with_temp_dir (self , setup_command , result_extension ,
204
238
result_directory , config_settings ):
@@ -235,6 +269,55 @@ def build_sdist(self, sdist_directory, config_settings=None):
235
269
'.tar.gz' , sdist_directory ,
236
270
config_settings )
237
271
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
+
238
321
239
322
class _BuildMetaLegacyBackend (_BuildMetaBackend ):
240
323
"""Compatibility backend for setuptools
@@ -281,9 +364,12 @@ def run_setup(self, setup_script='setup.py'):
281
364
282
365
get_requires_for_build_wheel = _BACKEND .get_requires_for_build_wheel
283
366
get_requires_for_build_sdist = _BACKEND .get_requires_for_build_sdist
367
+ get_requires_for_build_editable = _BACKEND .get_requires_for_build_editable
284
368
prepare_metadata_for_build_wheel = _BACKEND .prepare_metadata_for_build_wheel
369
+ prepare_metadata_for_build_editable = _BACKEND .prepare_metadata_for_build_wheel
285
370
build_wheel = _BACKEND .build_wheel
286
371
build_sdist = _BACKEND .build_sdist
372
+ build_editable = _BACKEND .build_editable
287
373
288
374
289
375
# The legacy backend
0 commit comments