Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / distribute-0.6.19-py2.7.egg / setuptools / command / bdist_egg.py @ 1a305335

History | View | Annotate | Download (17.9 KB)

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