Mercurial > repos > bcclaywell > argo_navis
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 |