Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(236)

Side by Side Diff: recipe_engine/third_party/setuptools/command/bdist_egg.py

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

Powered by Google App Engine
This is Rietveld 408576698