comparison lib/python3.8/site-packages/setuptools/command/bdist_egg.py @ 0:9e54283cc701 draft

"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author guerler
date Mon, 27 Jul 2020 03:47:31 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:9e54283cc701
1 """setuptools.command.bdist_egg
2
3 Build .egg distributions"""
4
5 from distutils.errors import DistutilsSetupError
6 from distutils.dir_util import remove_tree, mkpath
7 from distutils import log
8 from types import CodeType
9 import sys
10 import os
11 import re
12 import textwrap
13 import marshal
14 import warnings
15
16 from setuptools.extern import six
17
18 from pkg_resources import get_build_platform, Distribution, ensure_directory
19 from pkg_resources import EntryPoint
20 from setuptools.extension import Library
21 from setuptools import Command, SetuptoolsDeprecationWarning
22
23 try:
24 # Python 2.7 or >=3.2
25 from sysconfig import get_path, get_python_version
26
27 def _get_purelib():
28 return get_path("purelib")
29 except ImportError:
30 from distutils.sysconfig import get_python_lib, get_python_version
31
32 def _get_purelib():
33 return get_python_lib(False)
34
35
36 def strip_module(filename):
37 if '.' in filename:
38 filename = os.path.splitext(filename)[0]
39 if filename.endswith('module'):
40 filename = filename[:-6]
41 return filename
42
43
44 def sorted_walk(dir):
45 """Do os.walk in a reproducible way,
46 independent of indeterministic filesystem readdir order
47 """
48 for base, dirs, files in os.walk(dir):
49 dirs.sort()
50 files.sort()
51 yield base, dirs, files
52
53
54 def write_stub(resource, pyfile):
55 _stub_template = textwrap.dedent("""
56 def __bootstrap__():
57 global __bootstrap__, __loader__, __file__
58 import sys, pkg_resources, imp
59 __file__ = pkg_resources.resource_filename(__name__, %r)
60 __loader__ = None; del __bootstrap__, __loader__
61 imp.load_dynamic(__name__,__file__)
62 __bootstrap__()
63 """).lstrip()
64 with open(pyfile, 'w') as f:
65 f.write(_stub_template % resource)
66
67
68 class bdist_egg(Command):
69 description = "create an \"egg\" distribution"
70
71 user_options = [
72 ('bdist-dir=', 'b',
73 "temporary directory for creating the distribution"),
74 ('plat-name=', 'p', "platform name to embed in generated filenames "
75 "(default: %s)" % get_build_platform()),
76 ('exclude-source-files', None,
77 "remove all .py files from the generated egg"),
78 ('keep-temp', 'k',
79 "keep the pseudo-installation tree around after " +
80 "creating the distribution archive"),
81 ('dist-dir=', 'd',
82 "directory to put final built distributions in"),
83 ('skip-build', None,
84 "skip rebuilding everything (for testing/debugging)"),
85 ]
86
87 boolean_options = [
88 'keep-temp', 'skip-build', 'exclude-source-files'
89 ]
90
91 def initialize_options(self):
92 self.bdist_dir = None
93 self.plat_name = None
94 self.keep_temp = 0
95 self.dist_dir = None
96 self.skip_build = 0
97 self.egg_output = None
98 self.exclude_source_files = None
99
100 def finalize_options(self):
101 ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")
102 self.egg_info = ei_cmd.egg_info
103
104 if self.bdist_dir is None:
105 bdist_base = self.get_finalized_command('bdist').bdist_base
106 self.bdist_dir = os.path.join(bdist_base, 'egg')
107
108 if self.plat_name is None:
109 self.plat_name = get_build_platform()
110
111 self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
112
113 if self.egg_output is None:
114
115 # Compute filename of the output egg
116 basename = Distribution(
117 None, None, ei_cmd.egg_name, ei_cmd.egg_version,
118 get_python_version(),
119 self.distribution.has_ext_modules() and self.plat_name
120 ).egg_name()
121
122 self.egg_output = os.path.join(self.dist_dir, basename + '.egg')
123
124 def do_install_data(self):
125 # Hack for packages that install data to install's --install-lib
126 self.get_finalized_command('install').install_lib = self.bdist_dir
127
128 site_packages = os.path.normcase(os.path.realpath(_get_purelib()))
129 old, self.distribution.data_files = self.distribution.data_files, []
130
131 for item in old:
132 if isinstance(item, tuple) and len(item) == 2:
133 if os.path.isabs(item[0]):
134 realpath = os.path.realpath(item[0])
135 normalized = os.path.normcase(realpath)
136 if normalized == site_packages or normalized.startswith(
137 site_packages + os.sep
138 ):
139 item = realpath[len(site_packages) + 1:], item[1]
140 # XXX else: raise ???
141 self.distribution.data_files.append(item)
142
143 try:
144 log.info("installing package data to %s", self.bdist_dir)
145 self.call_command('install_data', force=0, root=None)
146 finally:
147 self.distribution.data_files = old
148
149 def get_outputs(self):
150 return [self.egg_output]
151
152 def call_command(self, cmdname, **kw):
153 """Invoke reinitialized command `cmdname` with keyword args"""
154 for dirname in INSTALL_DIRECTORY_ATTRS:
155 kw.setdefault(dirname, self.bdist_dir)
156 kw.setdefault('skip_build', self.skip_build)
157 kw.setdefault('dry_run', self.dry_run)
158 cmd = self.reinitialize_command(cmdname, **kw)
159 self.run_command(cmdname)
160 return cmd
161
162 def run(self):
163 # Generate metadata first
164 self.run_command("egg_info")
165 # We run install_lib before install_data, because some data hacks
166 # pull their data path from the install_lib command.
167 log.info("installing library code to %s", self.bdist_dir)
168 instcmd = self.get_finalized_command('install')
169 old_root = instcmd.root
170 instcmd.root = None
171 if self.distribution.has_c_libraries() and not self.skip_build:
172 self.run_command('build_clib')
173 cmd = self.call_command('install_lib', warn_dir=0)
174 instcmd.root = old_root
175
176 all_outputs, ext_outputs = self.get_ext_outputs()
177 self.stubs = []
178 to_compile = []
179 for (p, ext_name) in enumerate(ext_outputs):
180 filename, ext = os.path.splitext(ext_name)
181 pyfile = os.path.join(self.bdist_dir, strip_module(filename) +
182 '.py')
183 self.stubs.append(pyfile)
184 log.info("creating stub loader for %s", ext_name)
185 if not self.dry_run:
186 write_stub(os.path.basename(ext_name), pyfile)
187 to_compile.append(pyfile)
188 ext_outputs[p] = ext_name.replace(os.sep, '/')
189
190 if to_compile:
191 cmd.byte_compile(to_compile)
192 if self.distribution.data_files:
193 self.do_install_data()
194
195 # Make the EGG-INFO directory
196 archive_root = self.bdist_dir
197 egg_info = os.path.join(archive_root, 'EGG-INFO')
198 self.mkpath(egg_info)
199 if self.distribution.scripts:
200 script_dir = os.path.join(egg_info, 'scripts')
201 log.info("installing scripts to %s", script_dir)
202 self.call_command('install_scripts', install_dir=script_dir,
203 no_ep=1)
204
205 self.copy_metadata_to(egg_info)
206 native_libs = os.path.join(egg_info, "native_libs.txt")
207 if all_outputs:
208 log.info("writing %s", native_libs)
209 if not self.dry_run:
210 ensure_directory(native_libs)
211 libs_file = open(native_libs, 'wt')
212 libs_file.write('\n'.join(all_outputs))
213 libs_file.write('\n')
214 libs_file.close()
215 elif os.path.isfile(native_libs):
216 log.info("removing %s", native_libs)
217 if not self.dry_run:
218 os.unlink(native_libs)
219
220 write_safety_flag(
221 os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()
222 )
223
224 if os.path.exists(os.path.join(self.egg_info, 'depends.txt')):
225 log.warn(
226 "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n"
227 "Use the install_requires/extras_require setup() args instead."
228 )
229
230 if self.exclude_source_files:
231 self.zap_pyfiles()
232
233 # Make the archive
234 make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
235 dry_run=self.dry_run, mode=self.gen_header())
236 if not self.keep_temp:
237 remove_tree(self.bdist_dir, dry_run=self.dry_run)
238
239 # Add to 'Distribution.dist_files' so that the "upload" command works
240 getattr(self.distribution, 'dist_files', []).append(
241 ('bdist_egg', get_python_version(), self.egg_output))
242
243 def zap_pyfiles(self):
244 log.info("Removing .py files from temporary directory")
245 for base, dirs, files in walk_egg(self.bdist_dir):
246 for name in files:
247 path = os.path.join(base, name)
248
249 if name.endswith('.py'):
250 log.debug("Deleting %s", path)
251 os.unlink(path)
252
253 if base.endswith('__pycache__'):
254 path_old = path
255
256 pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'
257 m = re.match(pattern, name)
258 path_new = os.path.join(
259 base, os.pardir, m.group('name') + '.pyc')
260 log.info(
261 "Renaming file from [%s] to [%s]"
262 % (path_old, path_new))
263 try:
264 os.remove(path_new)
265 except OSError:
266 pass
267 os.rename(path_old, path_new)
268
269 def zip_safe(self):
270 safe = getattr(self.distribution, 'zip_safe', None)
271 if safe is not None:
272 return safe
273 log.warn("zip_safe flag not set; analyzing archive contents...")
274 return analyze_egg(self.bdist_dir, self.stubs)
275
276 def gen_header(self):
277 epm = EntryPoint.parse_map(self.distribution.entry_points or '')
278 ep = epm.get('setuptools.installation', {}).get('eggsecutable')
279 if ep is None:
280 return 'w' # not an eggsecutable, do it the usual way.
281
282 warnings.warn(
283 "Eggsecutables are deprecated and will be removed in a future "
284 "version.",
285 SetuptoolsDeprecationWarning
286 )
287
288 if not ep.attrs or ep.extras:
289 raise DistutilsSetupError(
290 "eggsecutable entry point (%r) cannot have 'extras' "
291 "or refer to a module" % (ep,)
292 )
293
294 pyver = '{}.{}'.format(*sys.version_info)
295 pkg = ep.module_name
296 full = '.'.join(ep.attrs)
297 base = ep.attrs[0]
298 basename = os.path.basename(self.egg_output)
299
300 header = (
301 "#!/bin/sh\n"
302 'if [ `basename $0` = "%(basename)s" ]\n'
303 'then exec python%(pyver)s -c "'
304 "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
305 "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
306 '" "$@"\n'
307 'else\n'
308 ' echo $0 is not the correct name for this egg file.\n'
309 ' echo Please rename it back to %(basename)s and try again.\n'
310 ' exec false\n'
311 'fi\n'
312 ) % locals()
313
314 if not self.dry_run:
315 mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
316 f = open(self.egg_output, 'w')
317 f.write(header)
318 f.close()
319 return 'a'
320
321 def copy_metadata_to(self, target_dir):
322 "Copy metadata (egg info) to the target_dir"
323 # normalize the path (so that a forward-slash in egg_info will
324 # match using startswith below)
325 norm_egg_info = os.path.normpath(self.egg_info)
326 prefix = os.path.join(norm_egg_info, '')
327 for path in self.ei_cmd.filelist.files:
328 if path.startswith(prefix):
329 target = os.path.join(target_dir, path[len(prefix):])
330 ensure_directory(target)
331 self.copy_file(path, target)
332
333 def get_ext_outputs(self):
334 """Get a list of relative paths to C extensions in the output distro"""
335
336 all_outputs = []
337 ext_outputs = []
338
339 paths = {self.bdist_dir: ''}
340 for base, dirs, files in sorted_walk(self.bdist_dir):
341 for filename in files:
342 if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
343 all_outputs.append(paths[base] + filename)
344 for filename in dirs:
345 paths[os.path.join(base, filename)] = (paths[base] +
346 filename + '/')
347
348 if self.distribution.has_ext_modules():
349 build_cmd = self.get_finalized_command('build_ext')
350 for ext in build_cmd.extensions:
351 if isinstance(ext, Library):
352 continue
353 fullname = build_cmd.get_ext_fullname(ext.name)
354 filename = build_cmd.get_ext_filename(fullname)
355 if not os.path.basename(filename).startswith('dl-'):
356 if os.path.exists(os.path.join(self.bdist_dir, filename)):
357 ext_outputs.append(filename)
358
359 return all_outputs, ext_outputs
360
361
362 NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())
363
364
365 def walk_egg(egg_dir):
366 """Walk an unpacked egg's contents, skipping the metadata directory"""
367 walker = sorted_walk(egg_dir)
368 base, dirs, files = next(walker)
369 if 'EGG-INFO' in dirs:
370 dirs.remove('EGG-INFO')
371 yield base, dirs, files
372 for bdf in walker:
373 yield bdf
374
375
376 def analyze_egg(egg_dir, stubs):
377 # check for existing flag in EGG-INFO
378 for flag, fn in safety_flags.items():
379 if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)):
380 return flag
381 if not can_scan():
382 return False
383 safe = True
384 for base, dirs, files in walk_egg(egg_dir):
385 for name in files:
386 if name.endswith('.py') or name.endswith('.pyw'):
387 continue
388 elif name.endswith('.pyc') or name.endswith('.pyo'):
389 # always scan, even if we already know we're not safe
390 safe = scan_module(egg_dir, base, name, stubs) and safe
391 return safe
392
393
394 def write_safety_flag(egg_dir, safe):
395 # Write or remove zip safety flag file(s)
396 for flag, fn in safety_flags.items():
397 fn = os.path.join(egg_dir, fn)
398 if os.path.exists(fn):
399 if safe is None or bool(safe) != flag:
400 os.unlink(fn)
401 elif safe is not None and bool(safe) == flag:
402 f = open(fn, 'wt')
403 f.write('\n')
404 f.close()
405
406
407 safety_flags = {
408 True: 'zip-safe',
409 False: 'not-zip-safe',
410 }
411
412
413 def scan_module(egg_dir, base, name, stubs):
414 """Check whether module possibly uses unsafe-for-zipfile stuff"""
415
416 filename = os.path.join(base, name)
417 if filename[:-1] in stubs:
418 return True # Extension module
419 pkg = base[len(egg_dir) + 1:].replace(os.sep, '.')
420 module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
421 if six.PY2:
422 skip = 8 # skip magic & date
423 elif sys.version_info < (3, 7):
424 skip = 12 # skip magic & date & file size
425 else:
426 skip = 16 # skip magic & reserved? & date & file size
427 f = open(filename, 'rb')
428 f.read(skip)
429 code = marshal.load(f)
430 f.close()
431 safe = True
432 symbols = dict.fromkeys(iter_symbols(code))
433 for bad in ['__file__', '__path__']:
434 if bad in symbols:
435 log.warn("%s: module references %s", module, bad)
436 safe = False
437 if 'inspect' in symbols:
438 for bad in [
439 'getsource', 'getabsfile', 'getsourcefile', 'getfile'
440 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',
441 'getinnerframes', 'getouterframes', 'stack', 'trace'
442 ]:
443 if bad in symbols:
444 log.warn("%s: module MAY be using inspect.%s", module, bad)
445 safe = False
446 return safe
447
448
449 def iter_symbols(code):
450 """Yield names and strings used by `code` and its nested code objects"""
451 for name in code.co_names:
452 yield name
453 for const in code.co_consts:
454 if isinstance(const, six.string_types):
455 yield const
456 elif isinstance(const, CodeType):
457 for name in iter_symbols(const):
458 yield name
459
460
461 def can_scan():
462 if not sys.platform.startswith('java') and sys.platform != 'cli':
463 # CPython, PyPy, etc.
464 return True
465 log.warn("Unable to analyze compiled code on this platform.")
466 log.warn("Please ask the author to include a 'zip_safe'"
467 " setting (either True or False) in the package's setup.py")
468
469
470 # Attribute names of options for commands that might need to be convinced to
471 # install to the egg build directory
472
473 INSTALL_DIRECTORY_ATTRS = [
474 'install_lib', 'install_dir', 'install_data', 'install_base'
475 ]
476
477
478 def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True,
479 mode='w'):
480 """Create a zip file from all the files under 'base_dir'. The output
481 zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
482 Python module (if available) or the InfoZIP "zip" utility (if installed
483 and found on the default search path). If neither tool is available,
484 raises DistutilsExecError. Returns the name of the output zip file.
485 """
486 import zipfile
487
488 mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
489 log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
490
491 def visit(z, dirname, names):
492 for name in names:
493 path = os.path.normpath(os.path.join(dirname, name))
494 if os.path.isfile(path):
495 p = path[len(base_dir) + 1:]
496 if not dry_run:
497 z.write(path, p)
498 log.debug("adding '%s'", p)
499
500 compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
501 if not dry_run:
502 z = zipfile.ZipFile(zip_filename, mode, compression=compression)
503 for dirname, dirs, files in sorted_walk(base_dir):
504 visit(z, dirname, files)
505 z.close()
506 else:
507 for dirname, dirs, files in sorted_walk(base_dir):
508 visit(None, dirname, files)
509 return zip_filename