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