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
"""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
#