#
# 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) and self._caseok(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 not rslt:
return None
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
if self.ispackage(importernm):
level -= 1
if level > 0:
importernm = string.join(string.split(importernm, '.')[:-level], ".")
contexts = [importernm, None]
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 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:
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
if ctypes and self.binaries:
self.binaries = _resolveCtypesImports(self.binaries)
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
def _resolveCtypesImports(cbinaries):
"""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(sys.pathex)
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
ret = []
# 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:
ret.append((cbin, cpath, "BINARY"))
_restorePaths(old)
return ret
|