mirror of
https://github.com/kennethreitz-archive/pyinstaller.git
synced 2026-06-05 23:50:17 +00:00
fc067fe1c3
It looks like the support was only preliminar and broken. Specifically: * mf.py was unable to look for dependencies within .eggs * iu.py was unable to use .egg file completely (!) Probably, it never fully worked. Anyway, it's completely fixed now (the setuptools tests pass) and I'm going to commit more tests soon (without the external dependency). git-svn-id: http://svn.pyinstaller.org/trunk@639 8dd32b29-ccff-0310-8a9a-9233e24343b1
1074 lines
37 KiB
Python
1074 lines
37 KiB
Python
#
|
|
# Copyright (C) 2005, Giovanni Bajo
|
|
#
|
|
# Based on previous work under copyright (c) 2002 McMillan Enterprises, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
#
|
|
|
|
import sys, string, os, imp, marshal, dircache, glob
|
|
try:
|
|
# zipimport is supported starting with Python 2.3
|
|
import zipimport
|
|
except ImportError:
|
|
zipimport = None
|
|
|
|
try:
|
|
# if ctypes is present, we can enable specific dependency discovery
|
|
import ctypes
|
|
from ctypes.util import find_library
|
|
except ImportError:
|
|
ctypes = None
|
|
|
|
import suffixes
|
|
|
|
try:
|
|
STRINGTYPE = basestring
|
|
except NameError:
|
|
STRINGTYPE = type("")
|
|
|
|
if not os.environ.has_key('PYTHONCASEOK') and sys.version_info >= (2, 1):
|
|
def caseOk(filename):
|
|
files = dircache.listdir(os.path.dirname(filename))
|
|
return os.path.basename(filename) in files
|
|
else:
|
|
def caseOk(filename):
|
|
return True
|
|
|
|
def pyco():
|
|
"""
|
|
Returns correct extension ending: 'c' or 'o'
|
|
"""
|
|
if __debug__:
|
|
return 'c'
|
|
else:
|
|
return 'o'
|
|
|
|
#=======================Owners==========================#
|
|
# An Owner does imports from a particular piece of turf
|
|
# That is, there's an Owner for each thing on sys.path
|
|
# There are owners for directories and .pyz files.
|
|
# There could be owners for zip files, or even URLs.
|
|
# Note that they replace the string in sys.path,
|
|
# but str(sys.path[n]) should yield the original string.
|
|
|
|
class OwnerError(Exception):
|
|
pass
|
|
|
|
class Owner:
|
|
def __init__(self, path, target_platform=None):
|
|
self.path = path
|
|
self.target_platform = target_platform
|
|
|
|
def __str__(self):
|
|
return self.path
|
|
|
|
def getmod(self, nm):
|
|
return None
|
|
|
|
class BaseDirOwner(Owner):
|
|
def _getsuffixes(self):
|
|
return suffixes.get_suffixes(self.target_platform)
|
|
|
|
def getmod(self, nm, getsuffixes=None, loadco=marshal.loads):
|
|
if getsuffixes is None:
|
|
getsuffixes = self._getsuffixes
|
|
possibles = [(nm, 0, None)]
|
|
if self._isdir(nm):
|
|
possibles.insert(0, (os.path.join(nm, '__init__'), 1, nm))
|
|
py = pyc = None
|
|
for pth, ispkg, pkgpth in possibles:
|
|
for ext, mode, typ in getsuffixes():
|
|
attempt = pth+ext
|
|
modtime = self._modtime(attempt)
|
|
if modtime is not None:
|
|
# Check case
|
|
if not self._caseok(attempt):
|
|
continue
|
|
if typ == imp.C_EXTENSION:
|
|
#print "DirOwner.getmod -> ExtensionModule(%s, %s)" % (nm, attempt)
|
|
return ExtensionModule(nm, os.path.join(self.path,attempt))
|
|
elif typ == imp.PY_SOURCE:
|
|
py = (attempt, modtime)
|
|
else:
|
|
pyc = (attempt, modtime)
|
|
if py or pyc:
|
|
break
|
|
if py is None and pyc is None:
|
|
#print "DirOwner.getmod -> (py == pyc == None)"
|
|
return None
|
|
while 1:
|
|
# If we have no pyc or py is newer
|
|
if pyc is None or py and pyc[1] < py[1]:
|
|
try:
|
|
stuff = self._read(py[0])+'\n'
|
|
co = compile(string.replace(stuff, "\r\n", "\n"), py[0], 'exec')
|
|
pth = py[0] + pyco()
|
|
break
|
|
except SyntaxError, e:
|
|
print "Syntax error in", py[0]
|
|
print e.args
|
|
raise
|
|
elif pyc:
|
|
stuff = self._read(pyc[0])
|
|
try:
|
|
co = loadco(stuff[8:])
|
|
pth = pyc[0]
|
|
break
|
|
except (ValueError, EOFError):
|
|
print "W: bad .pyc found (%s), will use .py" % pyc[0]
|
|
pyc = None
|
|
else:
|
|
#print "DirOwner.getmod while 1 -> None"
|
|
return None
|
|
pth = os.path.join(self.path, pth)
|
|
if not os.path.isabs(pth):
|
|
pth = os.path.abspath(pth)
|
|
if ispkg:
|
|
mod = self._pkgclass()(nm, pth, co)
|
|
else:
|
|
mod = self._modclass()(nm, pth, co)
|
|
#print "DirOwner.getmod -> %s" % mod
|
|
return mod
|
|
|
|
class DirOwner(BaseDirOwner):
|
|
def __init__(self, path, target_platform=None):
|
|
if path == '':
|
|
path = os.getcwd()
|
|
if not os.path.isdir(path):
|
|
raise OwnerError("%s is not a directory" % repr(path))
|
|
Owner.__init__(self, path, target_platform)
|
|
|
|
def _isdir(self, fn):
|
|
return os.path.isdir(os.path.join(self.path, fn))
|
|
def _modtime(self, fn):
|
|
try:
|
|
return os.stat(os.path.join(self.path, fn))[8]
|
|
except OSError:
|
|
return None
|
|
def _read(self, fn):
|
|
return open(os.path.join(self.path, fn), 'rb').read()
|
|
def _pkgclass(self):
|
|
return PkgModule
|
|
def _modclass(self):
|
|
return PyModule
|
|
def _caseok(self, fn):
|
|
return caseOk(os.path.join(self.path, fn))
|
|
|
|
class PYZOwner(Owner):
|
|
def __init__(self, path, target_platform=None):
|
|
import archive
|
|
self.pyz = archive.ZlibArchive(path)
|
|
Owner.__init__(self, path, target_platform)
|
|
def getmod(self, nm):
|
|
rslt = self.pyz.extract(nm)
|
|
if rslt:
|
|
ispkg, co = rslt
|
|
if ispkg:
|
|
return PkgInPYZModule(nm, co, self)
|
|
return PyModule(nm, self.path, co)
|
|
|
|
|
|
ZipOwner = None
|
|
if zipimport:
|
|
# We cannot use zipimporter here because it has a stupid bug:
|
|
#
|
|
# >>> z.find_module("setuptools.setuptools.setuptools.setuptools.setuptools") is not None
|
|
# True
|
|
#
|
|
# So mf will go into infinite recursion.
|
|
# Instead, we'll reuse the BaseDirOwner logic, simply changing
|
|
# the template methods.
|
|
class ZipOwner(BaseDirOwner):
|
|
def __init__(self, path, target_platform=None):
|
|
import zipfile
|
|
try:
|
|
self.zf = zipfile.ZipFile(path, "r")
|
|
except IOError:
|
|
raise OwnerError("%s is not a zipfile" % path)
|
|
Owner.__init__(self, path, target_platform)
|
|
def getmod(self, fn):
|
|
fn = fn.replace(".", "/")
|
|
return BaseDirOwner.getmod(self, fn)
|
|
def _modtime(self, fn):
|
|
fn = fn.replace("\\","/")
|
|
try:
|
|
dt = self.zf.getinfo(fn).date_time
|
|
return dt
|
|
except KeyError:
|
|
return None
|
|
def _isdir(self, fn):
|
|
# No way to find out if "fn" is a directory
|
|
# so just always look into it in case it is.
|
|
return True
|
|
def _caseok(self, fn):
|
|
# zipfile is always case-sensitive, so surely
|
|
# there is no case mismatch.
|
|
return True
|
|
def _read(self, fn):
|
|
# zipfiles always use forward slashes
|
|
fn = fn.replace("\\","/")
|
|
return self.zf.read(fn)
|
|
def _pkgclass(self):
|
|
return lambda *args: PkgInZipModule(self, *args)
|
|
def _modclass(self):
|
|
return lambda *args: PyInZipModule(self, *args)
|
|
|
|
_globalownertypes = filter(None, [
|
|
DirOwner,
|
|
ZipOwner,
|
|
PYZOwner,
|
|
Owner,
|
|
])
|
|
|
|
#===================Import Directors====================================#
|
|
# ImportDirectors live on the metapath
|
|
# There's one for builtins, one for frozen modules, and one for sys.path
|
|
# Windows gets one for modules gotten from the Registry
|
|
# There should be one for Frozen modules
|
|
# Mac would have them for PY_RESOURCE modules etc.
|
|
# A generalization of Owner - their concept of "turf" is broader
|
|
|
|
class ImportDirector(Owner):
|
|
pass
|
|
|
|
class BuiltinImportDirector(ImportDirector):
|
|
def __init__(self):
|
|
self.path = 'Builtins'
|
|
|
|
def getmod(self, nm, isbuiltin=imp.is_builtin):
|
|
if isbuiltin(nm):
|
|
return BuiltinModule(nm)
|
|
return None
|
|
|
|
class FrozenImportDirector(ImportDirector):
|
|
def __init__(self):
|
|
self.path = 'FrozenModules'
|
|
|
|
def getmod(self, nm, isfrozen=imp.is_frozen):
|
|
if isfrozen(nm):
|
|
return FrozenModule(nm)
|
|
return None
|
|
|
|
class RegistryImportDirector(ImportDirector):
|
|
# for Windows only
|
|
def __init__(self):
|
|
self.path = "WindowsRegistry"
|
|
self.map = {}
|
|
try:
|
|
import win32api
|
|
import win32con
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
subkey = r"Software\Python\PythonCore\%s\Modules" % sys.winver
|
|
for root in (win32con.HKEY_CURRENT_USER, win32con.HKEY_LOCAL_MACHINE):
|
|
try:
|
|
#hkey = win32api.RegOpenKeyEx(root, subkey, 0, win32con.KEY_ALL_ACCESS)
|
|
hkey = win32api.RegOpenKeyEx(root, subkey, 0, win32con.KEY_READ)
|
|
except Exception, e:
|
|
#print "RegistryImportDirector", e
|
|
pass
|
|
else:
|
|
numsubkeys, numvalues, lastmodified = win32api.RegQueryInfoKey(hkey)
|
|
for i in range(numsubkeys):
|
|
subkeyname = win32api.RegEnumKey(hkey, i)
|
|
#hskey = win32api.RegOpenKeyEx(hkey, subkeyname, 0, win32con.KEY_ALL_ACCESS)
|
|
hskey = win32api.RegOpenKeyEx(hkey, subkeyname, 0, win32con.KEY_READ)
|
|
val = win32api.RegQueryValueEx(hskey, '')
|
|
desc = getDescr(val[0])
|
|
#print " RegistryImportDirector got %s %s" % (val[0], desc) #XXX
|
|
self.map[subkeyname] = (val[0], desc)
|
|
hskey.Close()
|
|
hkey.Close()
|
|
break
|
|
|
|
def getmod(self, nm):
|
|
stuff = self.map.get(nm)
|
|
if stuff:
|
|
fnm, (suffix, mode, typ) = stuff
|
|
if typ == imp.C_EXTENSION:
|
|
return ExtensionModule(nm, fnm)
|
|
elif typ == imp.PY_SOURCE:
|
|
try:
|
|
stuff = open(fnm, 'r').read()+'\n'
|
|
co = compile(string.replace(stuff, "\r\n", "\n"), fnm, 'exec')
|
|
except SyntaxError, e:
|
|
print "Invalid syntax in %s" % py[0]
|
|
print e.args
|
|
raise
|
|
else:
|
|
stuff = open(fnm, 'rb').read()
|
|
co = loadco(stuff[8:])
|
|
return PyModule(nm, fnm, co)
|
|
return None
|
|
|
|
class PathImportDirector(ImportDirector):
|
|
def __init__(self, pathlist=None, importers=None, ownertypes=None,
|
|
target_platform=None):
|
|
if pathlist is None:
|
|
self.path = sys.path
|
|
else:
|
|
self.path = pathlist
|
|
if ownertypes == None:
|
|
self.ownertypes = _globalownertypes
|
|
else:
|
|
self.ownertypes = ownertypes
|
|
if importers:
|
|
self.shadowpath = importers
|
|
else:
|
|
self.shadowpath = {}
|
|
self.inMakeOwner = 0
|
|
self.building = {}
|
|
self.target_platform = target_platform
|
|
|
|
def __str__(self):
|
|
return str(self.path)
|
|
|
|
def getmod(self, nm):
|
|
mod = None
|
|
for thing in self.path:
|
|
if isinstance(thing, STRINGTYPE):
|
|
owner = self.shadowpath.get(thing, -1)
|
|
if owner == -1:
|
|
owner = self.shadowpath[thing] = self.makeOwner(thing)
|
|
if owner:
|
|
mod = owner.getmod(nm)
|
|
else:
|
|
mod = thing.getmod(nm)
|
|
if mod:
|
|
break
|
|
return mod
|
|
|
|
def makeOwner(self, path):
|
|
if self.building.get(path):
|
|
return None
|
|
self.building[path] = 1
|
|
owner = None
|
|
for klass in self.ownertypes:
|
|
try:
|
|
# this may cause an import, which may cause recursion
|
|
# hence the protection
|
|
owner = klass(path, self.target_platform)
|
|
except OwnerError:
|
|
pass
|
|
except Exception, e:
|
|
#print "FIXME: Wrong exception", e
|
|
pass
|
|
else:
|
|
break
|
|
del self.building[path]
|
|
return owner
|
|
|
|
|
|
def getDescr(fnm):
|
|
ext = os.path.splitext(fnm)[1]
|
|
for (suffix, mode, typ) in imp.get_suffixes():
|
|
if suffix == ext:
|
|
return (suffix, mode, typ)
|
|
|
|
#=================Import Tracker============================#
|
|
# This one doesn't really import, just analyzes
|
|
# If it *were* importing, it would be the one-and-only ImportManager
|
|
# ie, the builtin import
|
|
|
|
UNTRIED = -1
|
|
|
|
imptyps = ['top-level', 'conditional', 'delayed', 'delayed, conditional']
|
|
import hooks
|
|
|
|
if __debug__:
|
|
import sys
|
|
import UserDict
|
|
class LogDict(UserDict.UserDict):
|
|
count = 0
|
|
def __init__(self, *args):
|
|
UserDict.UserDict.__init__(self, *args)
|
|
LogDict.count += 1
|
|
self.logfile = open("logdict%s-%d.log" % (".".join(map(str, sys.version_info)),
|
|
LogDict.count), "w")
|
|
def __setitem__(self, key, value):
|
|
self.logfile.write("%s: %s -> %s\n" % (key, self.data.get(key), value))
|
|
UserDict.UserDict.__setitem__(self, key, value)
|
|
def __delitem__(self, key):
|
|
self.logfile.write(" DEL %s\n" % key)
|
|
UserDict.UserDict.__delitem__(self, key)
|
|
else:
|
|
LogDict = dict
|
|
|
|
|
|
class ImportTracker:
|
|
# really the equivalent of builtin import
|
|
def __init__(self, xpath=None, hookspath=None, excludes=None,
|
|
target_platform=None):
|
|
self.path = []
|
|
self.warnings = {}
|
|
if xpath:
|
|
self.path = xpath
|
|
self.path.extend(sys.path)
|
|
self.modules = LogDict()
|
|
self.metapath = [
|
|
BuiltinImportDirector(),
|
|
FrozenImportDirector(),
|
|
RegistryImportDirector(),
|
|
PathImportDirector(self.path, target_platform=target_platform)
|
|
]
|
|
if hookspath:
|
|
hooks.__path__.extend(hookspath)
|
|
self.excludes = excludes
|
|
if excludes is None:
|
|
self.excludes = []
|
|
self.target_platform = target_platform
|
|
|
|
def analyze_r(self, nm, importernm=None):
|
|
importer = importernm
|
|
if importer is None:
|
|
importer = '__main__'
|
|
seen = {}
|
|
nms = self.analyze_one(nm, importernm)
|
|
nms = map(None, nms, [importer]*len(nms))
|
|
i = 0
|
|
while i < len(nms):
|
|
nm, importer = nms[i]
|
|
if seen.get(nm,0):
|
|
del nms[i]
|
|
mod = self.modules[nm]
|
|
if mod:
|
|
mod.xref(importer)
|
|
else:
|
|
i = i + 1
|
|
seen[nm] = 1
|
|
j = i
|
|
mod = self.modules[nm]
|
|
if mod:
|
|
mod.xref(importer)
|
|
for name, isdelayed, isconditional, level in mod.imports:
|
|
imptyp = isdelayed * 2 + isconditional
|
|
newnms = self.analyze_one(name, nm, imptyp, level)
|
|
newnms = map(None, newnms, [nm]*len(newnms))
|
|
nms[j:j] = newnms
|
|
j = j + len(newnms)
|
|
return map(lambda a: a[0], nms)
|
|
|
|
def analyze_one(self, nm, importernm=None, imptyp=0, level=-1):
|
|
#print '## analyze_one', nm, importernm, imptyp, level
|
|
# break the name being imported up so we get:
|
|
# a.b.c -> [a, b, c] ; ..z -> ['', '', z]
|
|
if not nm:
|
|
nm = importernm
|
|
importernm = None
|
|
level = 0
|
|
nmparts = string.split(nm, '.')
|
|
|
|
if level < 0:
|
|
# behaviour up to Python 2.4 (and default in Python 2.5)
|
|
# first see if we could be importing a relative name
|
|
contexts = [None]
|
|
if importernm:
|
|
if self.ispackage(importernm):
|
|
contexts.insert(0, importernm)
|
|
else:
|
|
pkgnm = string.join(string.split(importernm, '.')[:-1], '.')
|
|
if pkgnm:
|
|
contexts.insert(0, pkgnm)
|
|
elif level == 0:
|
|
# absolute import, do not try relative
|
|
importernm = None
|
|
contexts = [None]
|
|
elif level > 0:
|
|
# relative import, do not try absolute
|
|
contexts = [string.join(string.split(importernm, '.')[:-level], '.')]
|
|
importernm = None
|
|
|
|
_all = None
|
|
|
|
assert contexts
|
|
# so contexts is [pkgnm, None] or just [None]
|
|
if nmparts[-1] == '*':
|
|
del nmparts[-1]
|
|
_all = []
|
|
nms = []
|
|
for context in contexts:
|
|
ctx = context
|
|
for i in range(len(nmparts)):
|
|
nm = nmparts[i]
|
|
if ctx:
|
|
fqname = ctx + '.' + nm
|
|
else:
|
|
fqname = nm
|
|
mod = self.modules.get(fqname, UNTRIED)
|
|
if mod is UNTRIED:
|
|
mod = self.doimport(nm, ctx, fqname)
|
|
if mod:
|
|
nms.append(mod.__name__)
|
|
ctx = fqname
|
|
else:
|
|
break
|
|
else:
|
|
# no break, point i beyond end
|
|
i = i + 1
|
|
if i:
|
|
break
|
|
# now nms is the list of modules that went into sys.modules
|
|
# just as result of the structure of the name being imported
|
|
# however, each mod has been scanned and that list is in mod.imports
|
|
if i<len(nmparts):
|
|
if ctx:
|
|
if hasattr(self.modules[ctx], nmparts[i]):
|
|
return nms
|
|
if not self.ispackage(ctx):
|
|
return nms
|
|
self.warnings["W: no module named %s (%s import by %s)" % (fqname, imptyps[imptyp], importernm or "__main__")] = 1
|
|
if self.modules.has_key(fqname):
|
|
del self.modules[fqname]
|
|
return nms
|
|
if _all is None:
|
|
return nms
|
|
bottommod = self.modules[ctx]
|
|
if bottommod.ispackage():
|
|
for nm in bottommod._all:
|
|
if not hasattr(bottommod, nm):
|
|
mod = self.doimport(nm, ctx, ctx+'.'+nm)
|
|
if mod:
|
|
nms.append(mod.__name__)
|
|
else:
|
|
bottommod.warnings.append("W: name %s not found" % nm)
|
|
return nms
|
|
|
|
def analyze_script(self, fnm):
|
|
try:
|
|
stuff = open(fnm, 'r').read()+'\n'
|
|
co = compile(string.replace(stuff, "\r\n", "\n"), fnm, 'exec')
|
|
except SyntaxError, e:
|
|
print "Invalid syntax in %s" % fnm
|
|
print e.args
|
|
raise
|
|
mod = PyScript(fnm, co)
|
|
self.modules['__main__'] = mod
|
|
return self.analyze_r('__main__')
|
|
|
|
|
|
def ispackage(self, nm):
|
|
return self.modules[nm].ispackage()
|
|
|
|
def _resolveCtypesImports(self, mod):
|
|
"""Completes ctypes BINARY entries for modules with their full path.
|
|
"""
|
|
if sys.platform.startswith("linux"):
|
|
envvar = "LD_LIBRARY_PATH"
|
|
elif sys.platform.startswith("darwin"):
|
|
envvar = "DYLD_LIBRARY_PATH"
|
|
else:
|
|
envvar = "PATH"
|
|
|
|
def _savePaths():
|
|
old = os.environ.get(envvar, None)
|
|
os.environ[envvar] = os.pathsep.join(self.path)
|
|
if old is not None:
|
|
os.environ[envvar] = os.pathsep.join([os.environ[envvar], old])
|
|
return old
|
|
|
|
def _restorePaths(old):
|
|
del os.environ[envvar]
|
|
if old is not None:
|
|
os.environ[envvar] = old
|
|
|
|
cbinaries = list(mod.binaries)
|
|
mod.binaries = []
|
|
|
|
# Try to locate the shared library on disk. This is done by
|
|
# executing ctypes.utile.find_library prepending ImportTracker's
|
|
# local paths to library search paths, then replaces original values.
|
|
old = _savePaths()
|
|
for cbin in cbinaries:
|
|
cpath = find_library(os.path.splitext(cbin)[0])
|
|
if sys.platform == "linux2":
|
|
# CAVEAT: find_library() is not the correct function. Ctype's
|
|
# documentation says that it is meant to resolve only the filename
|
|
# (as a *compiler* does) not the full path. Anyway, it works well
|
|
# enough on Windows and Mac. On Linux, we need to implement
|
|
# more code to find out the full path.
|
|
if cpath is None:
|
|
cpath = cbin
|
|
# "man ld.so" says that we should first search LD_LIBRARY_PATH
|
|
# and then the ldcache
|
|
for d in os.environ["LD_LIBRARY_PATH"].split(":"):
|
|
if os.path.isfile(d + "/" + cpath):
|
|
cpath = d + "/" + cpath
|
|
break
|
|
else:
|
|
for L in os.popen("ldconfig -p").read().splitlines():
|
|
if cpath in L:
|
|
cpath = L.split("=>", 1)[1].strip()
|
|
assert os.path.isfile(cpath)
|
|
break
|
|
else:
|
|
cpath = None
|
|
if cpath is None:
|
|
print "W: library %s required via ctypes not found" % (cbin,)
|
|
else:
|
|
mod.binaries.append((cbin, cpath, "BINARY"))
|
|
_restorePaths(old)
|
|
|
|
def doimport(self, nm, ctx, fqname):
|
|
#print "doimport", nm, ctx, fqname
|
|
# Not that nm is NEVER a dotted name at this point
|
|
assert ("." not in nm), nm
|
|
if fqname in self.excludes:
|
|
return None
|
|
if ctx:
|
|
parent = self.modules[ctx]
|
|
if parent.ispackage():
|
|
mod = parent.doimport(nm)
|
|
if mod:
|
|
# insert the new module in the parent package
|
|
# FIXME why?
|
|
setattr(parent, nm, mod)
|
|
else:
|
|
# if parent is not a package, there is nothing more to do
|
|
return None
|
|
else:
|
|
# now we're dealing with an absolute import
|
|
# try to import nm using available directors
|
|
for director in self.metapath:
|
|
mod = director.getmod(nm)
|
|
if mod:
|
|
break
|
|
# here we have `mod` from:
|
|
# mod = parent.doimport(nm)
|
|
# or
|
|
# mod = director.getmod(nm)
|
|
if mod:
|
|
if ctypes and isinstance(mod, PyModule):
|
|
self._resolveCtypesImports(mod)
|
|
mod.__name__ = fqname
|
|
self.modules[fqname] = mod
|
|
# now look for hooks
|
|
# this (and scan_code) are instead of doing "exec co in mod.__dict__"
|
|
try:
|
|
hookmodnm = 'hook-'+fqname
|
|
hooks = __import__('hooks', globals(), locals(), [hookmodnm])
|
|
hook = getattr(hooks, hookmodnm)
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
# rearranged so that hook() has a chance to mess with hiddenimports & attrs
|
|
if hasattr(hook, 'hook'):
|
|
mod = hook.hook(mod)
|
|
if hasattr(hook, 'hiddenimports'):
|
|
for impnm in hook.hiddenimports:
|
|
mod.imports.append((impnm, 0, 0, -1))
|
|
if hasattr(hook, 'attrs'):
|
|
for attr, val in hook.attrs:
|
|
setattr(mod, attr, val)
|
|
if hasattr(hook, 'datas'):
|
|
# hook.datas is a list of globs of files or directories to bundle
|
|
# as datafiles. For each glob, a destination directory is specified.
|
|
for g,dest_dir in hook.datas:
|
|
if dest_dir: dest_dir += "/"
|
|
for fn in glob.glob(g):
|
|
if os.path.isfile(fn):
|
|
mod.datas.append((dest_dir + os.path.basename(fn), fn, 'DATA'))
|
|
else:
|
|
def visit((base,dest_dir,datas), dirname, names):
|
|
for fn in names:
|
|
fn = os.path.join(dirname, fn)
|
|
if os.path.isfile(fn):
|
|
datas.append((dest_dir + fn[len(base)+1:], fn, 'DATA'))
|
|
os.path.walk(fn, visit, (os.path.dirname(fn),dest_dir,mod.datas))
|
|
if fqname != mod.__name__:
|
|
print "W: %s is changing it's name to %s" % (fqname, mod.__name__)
|
|
self.modules[mod.__name__] = mod
|
|
else:
|
|
assert (mod == None), mod
|
|
self.modules[fqname] = None
|
|
# should be equivalent using only one
|
|
# self.modules[fqname] = mod
|
|
# here
|
|
return mod
|
|
|
|
def getwarnings(self):
|
|
warnings = self.warnings.keys()
|
|
for nm,mod in self.modules.items():
|
|
if mod:
|
|
for w in mod.warnings:
|
|
warnings.append(w+' - %s (%s)' % (mod.__name__, mod.__file__))
|
|
return warnings
|
|
|
|
def getxref(self):
|
|
mods = self.modules.items() # (nm, mod)
|
|
mods.sort()
|
|
rslt = []
|
|
for nm, mod in mods:
|
|
if mod:
|
|
importers = mod._xref.keys()
|
|
importers.sort()
|
|
rslt.append((nm, importers))
|
|
return rslt
|
|
|
|
#====================Modules============================#
|
|
# All we're doing here is tracking, not importing
|
|
# If we were importing, these would be hooked to the real module objects
|
|
|
|
class Module:
|
|
_ispkg = 0
|
|
typ = 'UNKNOWN'
|
|
|
|
def __init__(self, nm):
|
|
self.__name__ = nm
|
|
self.__file__ = None
|
|
self._all = []
|
|
self.imports = []
|
|
self.warnings = []
|
|
self.binaries = []
|
|
self.datas = []
|
|
self._xref = {}
|
|
|
|
def ispackage(self):
|
|
return self._ispkg
|
|
|
|
def doimport(self, nm):
|
|
pass
|
|
|
|
def xref(self, nm):
|
|
self._xref[nm] = 1
|
|
|
|
def __str__(self):
|
|
return "<Module %s %s imports=%s binaries=%s datas=%s>" % \
|
|
(self.__name__, self.__file__, self.imports, self.binaries, self.datas)
|
|
|
|
class BuiltinModule(Module):
|
|
typ = 'BUILTIN'
|
|
|
|
def __init__(self, nm):
|
|
Module.__init__(self, nm)
|
|
|
|
class ExtensionModule(Module):
|
|
typ = 'EXTENSION'
|
|
|
|
def __init__(self, nm, pth):
|
|
Module.__init__(self, nm)
|
|
self.__file__ = pth
|
|
|
|
class PyModule(Module):
|
|
typ = 'PYMODULE'
|
|
|
|
def __init__(self, nm, pth, co):
|
|
Module.__init__(self, nm)
|
|
self.co = co
|
|
self.__file__ = pth
|
|
if os.path.splitext(self.__file__)[1] == '.py':
|
|
self.__file__ = self.__file__ + pyco()
|
|
self.scancode()
|
|
|
|
def scancode(self):
|
|
self.imports, self.warnings, self.binaries, allnms = scan_code(self.co)
|
|
if allnms:
|
|
self._all = allnms
|
|
|
|
class PyScript(PyModule):
|
|
typ = 'PYSOURCE'
|
|
|
|
def __init__(self, pth, co):
|
|
Module.__init__(self, '__main__')
|
|
self.co = co
|
|
self.__file__ = pth
|
|
self.scancode()
|
|
|
|
|
|
class PkgModule(PyModule):
|
|
typ = 'PYMODULE'
|
|
|
|
def __init__(self, nm, pth, co):
|
|
PyModule.__init__(self, nm, pth, co)
|
|
self._ispkg = 1
|
|
pth = os.path.dirname(pth)
|
|
self.__path__ = [ pth ]
|
|
self._update_director(force=True)
|
|
|
|
def _update_director(self, force=False):
|
|
if force or self.subimporter.path != self.__path__:
|
|
self.subimporter = PathImportDirector(self.__path__)
|
|
|
|
def doimport(self, nm):
|
|
self._update_director()
|
|
mod = self.subimporter.getmod(nm)
|
|
if mod:
|
|
mod.__name__ = self.__name__ + '.' + mod.__name__
|
|
return mod
|
|
|
|
class PkgInPYZModule(PyModule):
|
|
def __init__(self, nm, co, pyzowner):
|
|
PyModule.__init__(self, nm, co.co_filename, co)
|
|
self._ispkg = 1
|
|
self.__path__ = [ str(pyzowner) ]
|
|
self.owner = pyzowner
|
|
|
|
def doimport(self, nm):
|
|
mod = self.owner.getmod(self.__name__ + '.' + nm)
|
|
return mod
|
|
|
|
class PyInZipModule(PyModule):
|
|
typ = 'ZIPFILE'
|
|
def __init__(self, zipowner, nm, pth, co):
|
|
PyModule.__init__(self, nm, co.co_filename, co)
|
|
self.owner = zipowner
|
|
|
|
class PkgInZipModule(PyModule):
|
|
typ = 'ZIPFILE'
|
|
def __init__(self, zipowner, nm, pth, co):
|
|
PyModule.__init__(self, nm, co.co_filename, co)
|
|
self._ispkg = 1
|
|
self.__path__ = [ str(zipowner) ]
|
|
self.owner = zipowner
|
|
|
|
def doimport(self, nm):
|
|
mod = self.owner.getmod(self.__name__ + '.' + nm)
|
|
return mod
|
|
|
|
|
|
#======================== Utility ================================#
|
|
# Scan the code object for imports, __all__ and wierd stuff
|
|
|
|
import dis
|
|
IMPORT_NAME = dis.opname.index('IMPORT_NAME')
|
|
IMPORT_FROM = dis.opname.index('IMPORT_FROM')
|
|
try:
|
|
IMPORT_STAR = dis.opname.index('IMPORT_STAR')
|
|
except:
|
|
IMPORT_STAR = 999
|
|
STORE_NAME = dis.opname.index('STORE_NAME')
|
|
STORE_FAST = dis.opname.index('STORE_FAST')
|
|
STORE_GLOBAL = dis.opname.index('STORE_GLOBAL')
|
|
LOAD_GLOBAL = dis.opname.index('LOAD_GLOBAL')
|
|
LOAD_ATTR = dis.opname.index('LOAD_ATTR')
|
|
LOAD_NAME = dis.opname.index('LOAD_NAME')
|
|
EXEC_STMT = dis.opname.index('EXEC_STMT')
|
|
try:
|
|
SET_LINENO = dis.opname.index('SET_LINENO')
|
|
except ValueError:
|
|
SET_LINENO = 999
|
|
BUILD_LIST = dis.opname.index('BUILD_LIST')
|
|
LOAD_CONST = dis.opname.index('LOAD_CONST')
|
|
if getattr(sys, 'version_info', (0,0,0)) > (2,5,0):
|
|
LOAD_CONST_level = LOAD_CONST
|
|
else:
|
|
LOAD_CONST_level = 999
|
|
JUMP_IF_FALSE = dis.opname.index('JUMP_IF_FALSE')
|
|
JUMP_IF_TRUE = dis.opname.index('JUMP_IF_TRUE')
|
|
JUMP_FORWARD = dis.opname.index('JUMP_FORWARD')
|
|
try:
|
|
STORE_DEREF = dis.opname.index('STORE_DEREF')
|
|
except ValueError:
|
|
STORE_DEREF = 999
|
|
COND_OPS = [JUMP_IF_TRUE, JUMP_IF_FALSE]
|
|
STORE_OPS = [STORE_NAME, STORE_FAST, STORE_GLOBAL, STORE_DEREF]
|
|
#IMPORT_STAR -> IMPORT_NAME mod ; IMPORT_STAR
|
|
#JUMP_IF_FALSE / JUMP_IF_TRUE / JUMP_FORWARD
|
|
|
|
def pass1(code):
|
|
instrs = []
|
|
i = 0
|
|
n = len(code)
|
|
curline = 0
|
|
incondition = 0
|
|
out = 0
|
|
while i < n:
|
|
if i >= out:
|
|
incondition = 0
|
|
c = code[i]
|
|
i = i+1
|
|
op = ord(c)
|
|
if op >= dis.HAVE_ARGUMENT:
|
|
oparg = ord(code[i]) + ord(code[i+1])*256
|
|
i = i+2
|
|
else:
|
|
oparg = None
|
|
if not incondition and op in COND_OPS:
|
|
incondition = 1
|
|
out = i + oparg
|
|
elif incondition and op == JUMP_FORWARD:
|
|
out = max(out, i + oparg)
|
|
if op == SET_LINENO:
|
|
curline = oparg
|
|
else:
|
|
instrs.append((op, oparg, incondition, curline))
|
|
return instrs
|
|
|
|
def scan_code(co, m=None, w=None, b=None, nested=0):
|
|
instrs = pass1(co.co_code)
|
|
if m is None:
|
|
m = []
|
|
if w is None:
|
|
w = []
|
|
if b is None:
|
|
b = []
|
|
all = None
|
|
lastname = None
|
|
level = -1 # import-level, same behaviour as up to Python 2.4
|
|
for i in range(len(instrs)):
|
|
op, oparg, conditional, curline = instrs[i]
|
|
if op == IMPORT_NAME:
|
|
if level <= 0:
|
|
name = lastname = co.co_names[oparg]
|
|
else:
|
|
name = lastname = co.co_names[oparg]
|
|
#print 'import_name', name, `lastname`, level
|
|
m.append((name, nested, conditional, level))
|
|
elif op == IMPORT_FROM:
|
|
name = co.co_names[oparg]
|
|
#print 'import_from', name, `lastname`, level,
|
|
if level > 0 and (not lastname or lastname[-1:] == '.'):
|
|
name = lastname + name
|
|
else:
|
|
name = lastname + '.' + name
|
|
#print name
|
|
m.append((name, nested, conditional, level))
|
|
assert lastname is not None
|
|
elif op == IMPORT_STAR:
|
|
m.append((lastname+'.*', nested, conditional, level))
|
|
elif op == STORE_NAME:
|
|
if co.co_names[oparg] == "__all__":
|
|
j = i - 1
|
|
pop, poparg, pcondtl, pline = instrs[j]
|
|
if pop != BUILD_LIST:
|
|
w.append("W: __all__ is built strangely at line %s" % pline)
|
|
else:
|
|
all = []
|
|
while j > 0:
|
|
j = j - 1
|
|
pop, poparg, pcondtl, pline = instrs[j]
|
|
if pop == LOAD_CONST:
|
|
all.append(co.co_consts[poparg])
|
|
else:
|
|
break
|
|
elif op in STORE_OPS:
|
|
pass
|
|
elif op == LOAD_CONST_level:
|
|
# starting with Python 2.5, _each_ import is preceeded with a
|
|
# LOAD_CONST to indicate the relative level.
|
|
if isinstance(co.co_consts[oparg], (int, long)):
|
|
level = co.co_consts[oparg]
|
|
elif op == LOAD_GLOBAL:
|
|
name = co.co_names[oparg]
|
|
cndtl = ['', 'conditional'][conditional]
|
|
lvl = ['top-level', 'delayed'][nested]
|
|
if name == "__import__":
|
|
w.append("W: %s %s __import__ hack detected at line %s" % (lvl, cndtl, curline))
|
|
elif name == "eval":
|
|
w.append("W: %s %s eval hack detected at line %s" % (lvl, cndtl, curline))
|
|
elif op == EXEC_STMT:
|
|
cndtl = ['', 'conditional'][conditional]
|
|
lvl = ['top-level', 'delayed'][nested]
|
|
w.append("W: %s %s exec statement detected at line %s" % (lvl, cndtl, curline))
|
|
else:
|
|
lastname = None
|
|
|
|
if ctypes:
|
|
# ctypes scanning requires a scope wider than one bytecode instruction,
|
|
# so the code resides in a separate function for clarity.
|
|
ctypesb, ctypesw = scan_code_for_ctypes(co, instrs, i)
|
|
b.extend(ctypesb)
|
|
w.extend(ctypesw)
|
|
|
|
for c in co.co_consts:
|
|
if isinstance(c, type(co)):
|
|
# FIXME: "all" was not updated here nor returned. Was it the desired
|
|
# behaviour?
|
|
_, _, _, all_nested = scan_code(c, m, w, b, 1)
|
|
if all_nested:
|
|
all.extend(all_nested)
|
|
return m, w, b, all
|
|
|
|
def scan_code_for_ctypes(co, instrs, i):
|
|
"""Detects ctypes dependencies, using reasonable heuristics that should
|
|
cover most common ctypes usages; returns a tuple of two lists, one
|
|
containing names of binaries detected as dependencies, the other containing
|
|
warnings.
|
|
"""
|
|
|
|
def _libFromConst(i):
|
|
"""Extracts library name from an expected LOAD_CONST instruction and
|
|
appends it to local binaries list.
|
|
"""
|
|
op, oparg, conditional, curline = instrs[i]
|
|
if op == LOAD_CONST:
|
|
soname = co.co_consts[oparg]
|
|
b.append(soname)
|
|
|
|
b = []
|
|
|
|
op, oparg, conditional, curline = instrs[i]
|
|
|
|
if op in (LOAD_GLOBAL, LOAD_NAME):
|
|
name = co.co_names[oparg]
|
|
|
|
if name in ("CDLL", "WinDLL"):
|
|
# Guesses ctypes imports of this type: CDLL("library.so")
|
|
|
|
# LOAD_GLOBAL 0 (CDLL) <--- we "are" here right now
|
|
# LOAD_CONST 1 ('library.so')
|
|
|
|
_libFromConst(i+1)
|
|
|
|
elif name == "ctypes":
|
|
# Guesses ctypes imports of this type: ctypes.DLL("library.so")
|
|
|
|
# LOAD_GLOBAL 0 (ctypes) <--- we "are" here right now
|
|
# LOAD_ATTR 1 (CDLL)
|
|
# LOAD_CONST 1 ('library.so')
|
|
|
|
op2, oparg2, conditional2, curline2 = instrs[i+1]
|
|
if op2 == LOAD_ATTR:
|
|
if co.co_names[oparg2] in ("CDLL", "WinDLL"):
|
|
# Fetch next, and finally get the library name
|
|
_libFromConst(i+2)
|
|
|
|
elif name == ("cdll", "windll"):
|
|
# Guesses ctypes imports of these types:
|
|
|
|
# * cdll.library (only valid on Windows)
|
|
|
|
# LOAD_GLOBAL 0 (cdll) <--- we "are" here right now
|
|
# LOAD_ATTR 1 (library)
|
|
|
|
# * cdll.LoadLibrary("library.so")
|
|
|
|
# LOAD_GLOBAL 0 (cdll) <--- we "are" here right now
|
|
# LOAD_ATTR 1 (LoadLibrary)
|
|
# LOAD_CONST 1 ('library.so')
|
|
|
|
op2, oparg2, conditional2, curline2 = instrs[i+1]
|
|
if op2 == LOAD_ATTR:
|
|
if co.co_names[oparg2] != "LoadLibrary":
|
|
# First type
|
|
soname = co.co_names[oparg2] + ".dll"
|
|
b.append(soname)
|
|
else:
|
|
# Second type, needs to fetch one more instruction
|
|
_libFromConst(i+2)
|
|
|
|
# If any of the libraries has been requested with anything different from
|
|
# the bare filename, drop that entry and warn the user - pyinstaller would
|
|
# need to patch the compiled pyc file to make it work correctly!
|
|
|
|
w = []
|
|
for bin in list(b):
|
|
if bin != os.path.basename(bin):
|
|
b.remove(bin)
|
|
w.append("W: ignoring %s - ctypes imports only supported using bare filenames" % (bin,))
|
|
|
|
return b, w
|