comparison venv/lib/python2.7/site-packages/setuptools/command/bdist_egg.py @ 0:d67268158946 draft

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