aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools/command/bdist_egg.py
diff options
context:
space:
mode:
authorPJ Eby <distutils-sig@python.org>2005-07-10 16:24:26 +0000
committerPJ Eby <distutils-sig@python.org>2005-07-10 16:24:26 +0000
commit4b0b1262dced5aab98a18fda75e8e43ae40e28d8 (patch)
treeb39534799b89141eb2f5cdb274750a4de8a3278d /setuptools/command/bdist_egg.py
parent39fc2a15c4a236ae7b378ebb98aee545776d2ec1 (diff)
downloadexternal_python_setuptools-4b0b1262dced5aab98a18fda75e8e43ae40e28d8.tar.gz
external_python_setuptools-4b0b1262dced5aab98a18fda75e8e43ae40e28d8.tar.bz2
external_python_setuptools-4b0b1262dced5aab98a18fda75e8e43ae40e28d8.zip
First-pass implementation of zippability analysis; scans for impure
distribution or use of __file__/__path__ and selected 'inspect' operations. Currently, the analysis is a bit overconservative; when the runtime is more robust, it should probably allow extensions to be zipped by default. --HG-- branch : setuptools extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041114
Diffstat (limited to 'setuptools/command/bdist_egg.py')
-rw-r--r--setuptools/command/bdist_egg.py119
1 files changed, 80 insertions, 39 deletions
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 21dcc464..621bbb18 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -3,13 +3,13 @@
Build .egg distributions"""
# This module should be kept compatible with Python 2.3
-import os
+import os, marshal
from setuptools import Command
from distutils.dir_util import remove_tree, mkpath
from distutils.sysconfig import get_python_version, get_python_lib
from distutils import log
from pkg_resources import get_platform, Distribution
-
+from types import CodeType
def write_stub(resource, pyfile):
f = open(pyfile,'w')
@@ -175,11 +175,12 @@ class bdist_egg(Command):
ext_outputs = cmd._mutate_outputs(
self.distribution.has_ext_modules(), 'build_ext', 'build_lib', ''
)
-
+ self.stubs = []
to_compile = []
for (p,ext_name) in enumerate(ext_outputs):
filename,ext = os.path.splitext(ext_name)
pyfile = os.path.join(self.bdist_dir, filename + '.py')
+ self.stubs.append(pyfile)
log.info("creating stub loader for %s" % ext_name)
if not self.dry_run:
write_stub(os.path.basename(ext_name), pyfile)
@@ -202,7 +203,6 @@ class bdist_egg(Command):
log.info("installing scripts to %s" % script_dir)
self.call_command('install_scripts', install_dir=script_dir)
-
native_libs = os.path.join(self.egg_info,"native_libs.txt")
if ext_outputs:
log.info("writing %s" % native_libs)
@@ -221,6 +221,10 @@ class bdist_egg(Command):
if os.path.isfile(path):
self.copy_file(path,os.path.join(egg_info,filename))
+ # Write a zip safety flag file
+ flag = self.zip_safe() and 'zip-safe' or 'not-zip-safe'
+ open(os.path.join(archive_root,'EGG-INFO',flag),'w').close()
+
if os.path.exists(os.path.join(self.egg_info,'depends.txt')):
log.warn(
"WARNING: 'depends.txt' will not be used by setuptools 0.6!\n"
@@ -240,49 +244,86 @@ class bdist_egg(Command):
('bdist_egg',get_python_version(),self.egg_output))
-
-
-
-
def zap_pyfiles(self):
log.info("Removing .py files from temporary directory")
- for base,dirs,files in os.walk(self.bdist_dir):
- if 'EGG-INFO' in dirs:
- dirs.remove('EGG-INFO')
+ for base,dirs,files in self.walk_contents():
for name in files:
if name.endswith('.py'):
path = os.path.join(base,name)
log.debug("Deleting %s", path)
os.unlink(path)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ def walk_contents(self):
+ """Walk files about to be archived, skipping the metadata directory"""
+ walker = os.walk(self.bdist_dir)
+ base,dirs,files = walker.next()
+ if 'EGG-INFO' in dirs:
+ dirs.remove('EGG-INFO')
+
+ yield base,dirs,files
+ for bdf in walker:
+ yield bdf
+
+ def zip_safe(self):
+ safe = getattr(self.distribution,'zip_safe',None)
+ if safe is not None:
+ return safe
+ log.warn("zip_safe flag not set; analyzing archive contents...")
+ safe = True
+ for base, dirs, files in self.walk_contents():
+ for name in files:
+ if name.endswith('.py') or name.endswith('.pyw'):
+ continue
+ elif name.endswith('.pyc') or name.endswith('.pyo'):
+ # always scan, even if we already know we're not safe
+ safe = self.scan_module(base, name) and safe
+ elif safe:
+ log.warn(
+ "Distribution contains data or extensions; assuming "
+ "it's unsafe (set zip_safe=True in setup() to change"
+ )
+ safe = False # XXX
+ return safe
+
+ def scan_module(self, base, name):
+ """Check whether module possibly uses unsafe-for-zipfile stuff"""
+ filename = os.path.join(base,name)
+ if filename[:-1] in self.stubs:
+ return True # Extension module
+
+ pkg = base[len(self.bdist_dir)+1:].replace(os.sep,'.')
+ module = pkg+(pkg and '.' or '')+os.path.splitext(name)[0]
+
+ f = open(filename,'rb'); f.read(8) # skip magic & date
+ code = marshal.load(f); f.close()
+ safe = True
+
+ symbols = dict.fromkeys(iter_symbols(code))
+ for bad in ['__file__', '__path__']:
+ if bad in symbols:
+ log.warn("%s: module references %s", module, bad)
+ safe = False
+ if 'inspect' in symbols:
+ for bad in [
+ 'getsource', 'getabsfile', 'getsourcefile', 'getfile'
+ 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',
+ 'getinnerframes', 'getouterframes', 'stack', 'trace'
+ ]:
+ if bad in symbols:
+ log.warn("%s: module MAY be using inspect.%s", module, bad)
+ safe = False
+ return safe
+
+
+def iter_symbols(code):
+ """Yield names and strings used by `code` and its nested code objects"""
+ for name in code.co_names: yield name
+ for const in code.co_consts:
+ if isinstance(const,basestring):
+ yield const
+ elif isinstance(const,CodeType):
+ for name in iter_symbols(const):
+ yield name
# Attribute names of options for commands that might need to be convinced to