+++ /dev/null
-"""Find modules used by a script, using introspection."""\r
-# This module should be kept compatible with Python 2.2, see PEP 291.\r
-\r
-from __future__ import generators\r
-import dis\r
-import imp\r
-import marshal\r
-import os\r
-import sys\r
-import types\r
-import struct\r
-\r
-if hasattr(sys.__stdout__, "newlines"):\r
- READ_MODE = "U" # universal line endings\r
-else:\r
- # remain compatible with Python < 2.3\r
- READ_MODE = "r"\r
-\r
-LOAD_CONST = chr(dis.opname.index('LOAD_CONST'))\r
-IMPORT_NAME = chr(dis.opname.index('IMPORT_NAME'))\r
-STORE_NAME = chr(dis.opname.index('STORE_NAME'))\r
-STORE_GLOBAL = chr(dis.opname.index('STORE_GLOBAL'))\r
-STORE_OPS = [STORE_NAME, STORE_GLOBAL]\r
-HAVE_ARGUMENT = chr(dis.HAVE_ARGUMENT)\r
-\r
-# Modulefinder does a good job at simulating Python's, but it can not\r
-# handle __path__ modifications packages make at runtime. Therefore there\r
-# is a mechanism whereby you can register extra paths in this map for a\r
-# package, and it will be honored.\r
-\r
-# Note this is a mapping is lists of paths.\r
-packagePathMap = {}\r
-\r
-# A Public interface\r
-def AddPackagePath(packagename, path):\r
- paths = packagePathMap.get(packagename, [])\r
- paths.append(path)\r
- packagePathMap[packagename] = paths\r
-\r
-replacePackageMap = {}\r
-\r
-# This ReplacePackage mechanism allows modulefinder to work around the\r
-# way the _xmlplus package injects itself under the name "xml" into\r
-# sys.modules at runtime by calling ReplacePackage("_xmlplus", "xml")\r
-# before running ModuleFinder.\r
-\r
-def ReplacePackage(oldname, newname):\r
- replacePackageMap[oldname] = newname\r
-\r
-\r
-class Module:\r
-\r
- def __init__(self, name, file=None, path=None):\r
- self.__name__ = name\r
- self.__file__ = file\r
- self.__path__ = path\r
- self.__code__ = None\r
- # The set of global names that are assigned to in the module.\r
- # This includes those names imported through starimports of\r
- # Python modules.\r
- self.globalnames = {}\r
- # The set of starimports this module did that could not be\r
- # resolved, ie. a starimport from a non-Python module.\r
- self.starimports = {}\r
-\r
- def __repr__(self):\r
- s = "Module(%r" % (self.__name__,)\r
- if self.__file__ is not None:\r
- s = s + ", %r" % (self.__file__,)\r
- if self.__path__ is not None:\r
- s = s + ", %r" % (self.__path__,)\r
- s = s + ")"\r
- return s\r
-\r
-class ModuleFinder:\r
-\r
- def __init__(self, path=None, debug=0, excludes=[], replace_paths=[]):\r
- if path is None:\r
- path = sys.path\r
- self.path = path\r
- self.modules = {}\r
- self.badmodules = {}\r
- self.debug = debug\r
- self.indent = 0\r
- self.excludes = excludes\r
- self.replace_paths = replace_paths\r
- self.processed_paths = [] # Used in debugging only\r
-\r
- def msg(self, level, str, *args):\r
- if level <= self.debug:\r
- for i in range(self.indent):\r
- print " ",\r
- print str,\r
- for arg in args:\r
- print repr(arg),\r
- print\r
-\r
- def msgin(self, *args):\r
- level = args[0]\r
- if level <= self.debug:\r
- self.indent = self.indent + 1\r
- self.msg(*args)\r
-\r
- def msgout(self, *args):\r
- level = args[0]\r
- if level <= self.debug:\r
- self.indent = self.indent - 1\r
- self.msg(*args)\r
-\r
- def run_script(self, pathname):\r
- self.msg(2, "run_script", pathname)\r
- with open(pathname, READ_MODE) as fp:\r
- stuff = ("", "r", imp.PY_SOURCE)\r
- self.load_module('__main__', fp, pathname, stuff)\r
-\r
- def load_file(self, pathname):\r
- dir, name = os.path.split(pathname)\r
- name, ext = os.path.splitext(name)\r
- with open(pathname, READ_MODE) as fp:\r
- stuff = (ext, "r", imp.PY_SOURCE)\r
- self.load_module(name, fp, pathname, stuff)\r
-\r
- def import_hook(self, name, caller=None, fromlist=None, level=-1):\r
- self.msg(3, "import_hook", name, caller, fromlist, level)\r
- parent = self.determine_parent(caller, level=level)\r
- q, tail = self.find_head_package(parent, name)\r
- m = self.load_tail(q, tail)\r
- if not fromlist:\r
- return q\r
- if m.__path__:\r
- self.ensure_fromlist(m, fromlist)\r
- return None\r
-\r
- def determine_parent(self, caller, level=-1):\r
- self.msgin(4, "determine_parent", caller, level)\r
- if not caller or level == 0:\r
- self.msgout(4, "determine_parent -> None")\r
- return None\r
- pname = caller.__name__\r
- if level >= 1: # relative import\r
- if caller.__path__:\r
- level -= 1\r
- if level == 0:\r
- parent = self.modules[pname]\r
- assert parent is caller\r
- self.msgout(4, "determine_parent ->", parent)\r
- return parent\r
- if pname.count(".") < level:\r
- raise ImportError, "relative importpath too deep"\r
- pname = ".".join(pname.split(".")[:-level])\r
- parent = self.modules[pname]\r
- self.msgout(4, "determine_parent ->", parent)\r
- return parent\r
- if caller.__path__:\r
- parent = self.modules[pname]\r
- assert caller is parent\r
- self.msgout(4, "determine_parent ->", parent)\r
- return parent\r
- if '.' in pname:\r
- i = pname.rfind('.')\r
- pname = pname[:i]\r
- parent = self.modules[pname]\r
- assert parent.__name__ == pname\r
- self.msgout(4, "determine_parent ->", parent)\r
- return parent\r
- self.msgout(4, "determine_parent -> None")\r
- return None\r
-\r
- def find_head_package(self, parent, name):\r
- self.msgin(4, "find_head_package", parent, name)\r
- if '.' in name:\r
- i = name.find('.')\r
- head = name[:i]\r
- tail = name[i+1:]\r
- else:\r
- head = name\r
- tail = ""\r
- if parent:\r
- qname = "%s.%s" % (parent.__name__, head)\r
- else:\r
- qname = head\r
- q = self.import_module(head, qname, parent)\r
- if q:\r
- self.msgout(4, "find_head_package ->", (q, tail))\r
- return q, tail\r
- if parent:\r
- qname = head\r
- parent = None\r
- q = self.import_module(head, qname, parent)\r
- if q:\r
- self.msgout(4, "find_head_package ->", (q, tail))\r
- return q, tail\r
- self.msgout(4, "raise ImportError: No module named", qname)\r
- raise ImportError, "No module named " + qname\r
-\r
- def load_tail(self, q, tail):\r
- self.msgin(4, "load_tail", q, tail)\r
- m = q\r
- while tail:\r
- i = tail.find('.')\r
- if i < 0: i = len(tail)\r
- head, tail = tail[:i], tail[i+1:]\r
- mname = "%s.%s" % (m.__name__, head)\r
- m = self.import_module(head, mname, m)\r
- if not m:\r
- self.msgout(4, "raise ImportError: No module named", mname)\r
- raise ImportError, "No module named " + mname\r
- self.msgout(4, "load_tail ->", m)\r
- return m\r
-\r
- def ensure_fromlist(self, m, fromlist, recursive=0):\r
- self.msg(4, "ensure_fromlist", m, fromlist, recursive)\r
- for sub in fromlist:\r
- if sub == "*":\r
- if not recursive:\r
- all = self.find_all_submodules(m)\r
- if all:\r
- self.ensure_fromlist(m, all, 1)\r
- elif not hasattr(m, sub):\r
- subname = "%s.%s" % (m.__name__, sub)\r
- submod = self.import_module(sub, subname, m)\r
- if not submod:\r
- raise ImportError, "No module named " + subname\r
-\r
- def find_all_submodules(self, m):\r
- if not m.__path__:\r
- return\r
- modules = {}\r
- # 'suffixes' used to be a list hardcoded to [".py", ".pyc", ".pyo"].\r
- # But we must also collect Python extension modules - although\r
- # we cannot separate normal dlls from Python extensions.\r
- suffixes = []\r
- for triple in imp.get_suffixes():\r
- suffixes.append(triple[0])\r
- for dir in m.__path__:\r
- try:\r
- names = os.listdir(dir)\r
- except os.error:\r
- self.msg(2, "can't list directory", dir)\r
- continue\r
- for name in names:\r
- mod = None\r
- for suff in suffixes:\r
- n = len(suff)\r
- if name[-n:] == suff:\r
- mod = name[:-n]\r
- break\r
- if mod and mod != "__init__":\r
- modules[mod] = mod\r
- return modules.keys()\r
-\r
- def import_module(self, partname, fqname, parent):\r
- self.msgin(3, "import_module", partname, fqname, parent)\r
- try:\r
- m = self.modules[fqname]\r
- except KeyError:\r
- pass\r
- else:\r
- self.msgout(3, "import_module ->", m)\r
- return m\r
- if fqname in self.badmodules:\r
- self.msgout(3, "import_module -> None")\r
- return None\r
- if parent and parent.__path__ is None:\r
- self.msgout(3, "import_module -> None")\r
- return None\r
- try:\r
- fp, pathname, stuff = self.find_module(partname,\r
- parent and parent.__path__, parent)\r
- except ImportError:\r
- self.msgout(3, "import_module ->", None)\r
- return None\r
- try:\r
- m = self.load_module(fqname, fp, pathname, stuff)\r
- finally:\r
- if fp: fp.close()\r
- if parent:\r
- setattr(parent, partname, m)\r
- self.msgout(3, "import_module ->", m)\r
- return m\r
-\r
- def load_module(self, fqname, fp, pathname, file_info):\r
- suffix, mode, type = file_info\r
- self.msgin(2, "load_module", fqname, fp and "fp", pathname)\r
- if type == imp.PKG_DIRECTORY:\r
- m = self.load_package(fqname, pathname)\r
- self.msgout(2, "load_module ->", m)\r
- return m\r
- if type == imp.PY_SOURCE:\r
- co = compile(fp.read()+'\n', pathname, 'exec')\r
- elif type == imp.PY_COMPILED:\r
- if fp.read(4) != imp.get_magic():\r
- self.msgout(2, "raise ImportError: Bad magic number", pathname)\r
- raise ImportError, "Bad magic number in %s" % pathname\r
- fp.read(4)\r
- co = marshal.load(fp)\r
- else:\r
- co = None\r
- m = self.add_module(fqname)\r
- m.__file__ = pathname\r
- if co:\r
- if self.replace_paths:\r
- co = self.replace_paths_in_code(co)\r
- m.__code__ = co\r
- self.scan_code(co, m)\r
- self.msgout(2, "load_module ->", m)\r
- return m\r
-\r
- def _add_badmodule(self, name, caller):\r
- if name not in self.badmodules:\r
- self.badmodules[name] = {}\r
- if caller:\r
- self.badmodules[name][caller.__name__] = 1\r
- else:\r
- self.badmodules[name]["-"] = 1\r
-\r
- def _safe_import_hook(self, name, caller, fromlist, level=-1):\r
- # wrapper for self.import_hook() that won't raise ImportError\r
- if name in self.badmodules:\r
- self._add_badmodule(name, caller)\r
- return\r
- try:\r
- self.import_hook(name, caller, level=level)\r
- except ImportError, msg:\r
- self.msg(2, "ImportError:", str(msg))\r
- self._add_badmodule(name, caller)\r
- else:\r
- if fromlist:\r
- for sub in fromlist:\r
- if sub in self.badmodules:\r
- self._add_badmodule(sub, caller)\r
- continue\r
- try:\r
- self.import_hook(name, caller, [sub], level=level)\r
- except ImportError, msg:\r
- self.msg(2, "ImportError:", str(msg))\r
- fullname = name + "." + sub\r
- self._add_badmodule(fullname, caller)\r
-\r
- def scan_opcodes(self, co,\r
- unpack = struct.unpack):\r
- # Scan the code, and yield 'interesting' opcode combinations\r
- # Version for Python 2.4 and older\r
- code = co.co_code\r
- names = co.co_names\r
- consts = co.co_consts\r
- while code:\r
- c = code[0]\r
- if c in STORE_OPS:\r
- oparg, = unpack('<H', code[1:3])\r
- yield "store", (names[oparg],)\r
- code = code[3:]\r
- continue\r
- if c == LOAD_CONST and code[3] == IMPORT_NAME:\r
- oparg_1, oparg_2 = unpack('<xHxH', code[:6])\r
- yield "import", (consts[oparg_1], names[oparg_2])\r
- code = code[6:]\r
- continue\r
- if c >= HAVE_ARGUMENT:\r
- code = code[3:]\r
- else:\r
- code = code[1:]\r
-\r
- def scan_opcodes_25(self, co,\r
- unpack = struct.unpack):\r
- # Scan the code, and yield 'interesting' opcode combinations\r
- # Python 2.5 version (has absolute and relative imports)\r
- code = co.co_code\r
- names = co.co_names\r
- consts = co.co_consts\r
- LOAD_LOAD_AND_IMPORT = LOAD_CONST + LOAD_CONST + IMPORT_NAME\r
- while code:\r
- c = code[0]\r
- if c in STORE_OPS:\r
- oparg, = unpack('<H', code[1:3])\r
- yield "store", (names[oparg],)\r
- code = code[3:]\r
- continue\r
- if code[:9:3] == LOAD_LOAD_AND_IMPORT:\r
- oparg_1, oparg_2, oparg_3 = unpack('<xHxHxH', code[:9])\r
- level = consts[oparg_1]\r
- if level == -1: # normal import\r
- yield "import", (consts[oparg_2], names[oparg_3])\r
- elif level == 0: # absolute import\r
- yield "absolute_import", (consts[oparg_2], names[oparg_3])\r
- else: # relative import\r
- yield "relative_import", (level, consts[oparg_2], names[oparg_3])\r
- code = code[9:]\r
- continue\r
- if c >= HAVE_ARGUMENT:\r
- code = code[3:]\r
- else:\r
- code = code[1:]\r
-\r
- def scan_code(self, co, m):\r
- code = co.co_code\r
- if sys.version_info >= (2, 5):\r
- scanner = self.scan_opcodes_25\r
- else:\r
- scanner = self.scan_opcodes\r
- for what, args in scanner(co):\r
- if what == "store":\r
- name, = args\r
- m.globalnames[name] = 1\r
- elif what in ("import", "absolute_import"):\r
- fromlist, name = args\r
- have_star = 0\r
- if fromlist is not None:\r
- if "*" in fromlist:\r
- have_star = 1\r
- fromlist = [f for f in fromlist if f != "*"]\r
- if what == "absolute_import": level = 0\r
- else: level = -1\r
- self._safe_import_hook(name, m, fromlist, level=level)\r
- if have_star:\r
- # We've encountered an "import *". If it is a Python module,\r
- # the code has already been parsed and we can suck out the\r
- # global names.\r
- mm = None\r
- if m.__path__:\r
- # At this point we don't know whether 'name' is a\r
- # submodule of 'm' or a global module. Let's just try\r
- # the full name first.\r
- mm = self.modules.get(m.__name__ + "." + name)\r
- if mm is None:\r
- mm = self.modules.get(name)\r
- if mm is not None:\r
- m.globalnames.update(mm.globalnames)\r
- m.starimports.update(mm.starimports)\r
- if mm.__code__ is None:\r
- m.starimports[name] = 1\r
- else:\r
- m.starimports[name] = 1\r
- elif what == "relative_import":\r
- level, fromlist, name = args\r
- if name:\r
- self._safe_import_hook(name, m, fromlist, level=level)\r
- else:\r
- parent = self.determine_parent(m, level=level)\r
- self._safe_import_hook(parent.__name__, None, fromlist, level=0)\r
- else:\r
- # We don't expect anything else from the generator.\r
- raise RuntimeError(what)\r
-\r
- for c in co.co_consts:\r
- if isinstance(c, type(co)):\r
- self.scan_code(c, m)\r
-\r
- def load_package(self, fqname, pathname):\r
- self.msgin(2, "load_package", fqname, pathname)\r
- newname = replacePackageMap.get(fqname)\r
- if newname:\r
- fqname = newname\r
- m = self.add_module(fqname)\r
- m.__file__ = pathname\r
- m.__path__ = [pathname]\r
-\r
- # As per comment at top of file, simulate runtime __path__ additions.\r
- m.__path__ = m.__path__ + packagePathMap.get(fqname, [])\r
-\r
- fp, buf, stuff = self.find_module("__init__", m.__path__)\r
- self.load_module(fqname, fp, buf, stuff)\r
- self.msgout(2, "load_package ->", m)\r
- if fp:\r
- fp.close()\r
- return m\r
-\r
- def add_module(self, fqname):\r
- if fqname in self.modules:\r
- return self.modules[fqname]\r
- self.modules[fqname] = m = Module(fqname)\r
- return m\r
-\r
- def find_module(self, name, path, parent=None):\r
- if parent is not None:\r
- # assert path is not None\r
- fullname = parent.__name__+'.'+name\r
- else:\r
- fullname = name\r
- if fullname in self.excludes:\r
- self.msgout(3, "find_module -> Excluded", fullname)\r
- raise ImportError, name\r
-\r
- if path is None:\r
- if name in sys.builtin_module_names:\r
- return (None, None, ("", "", imp.C_BUILTIN))\r
-\r
- path = self.path\r
- return imp.find_module(name, path)\r
-\r
- def report(self):\r
- """Print a report to stdout, listing the found modules with their\r
- paths, as well as modules that are missing, or seem to be missing.\r
- """\r
- print\r
- print " %-25s %s" % ("Name", "File")\r
- print " %-25s %s" % ("----", "----")\r
- # Print modules found\r
- keys = self.modules.keys()\r
- keys.sort()\r
- for key in keys:\r
- m = self.modules[key]\r
- if m.__path__:\r
- print "P",\r
- else:\r
- print "m",\r
- print "%-25s" % key, m.__file__ or ""\r
-\r
- # Print missing modules\r
- missing, maybe = self.any_missing_maybe()\r
- if missing:\r
- print\r
- print "Missing modules:"\r
- for name in missing:\r
- mods = self.badmodules[name].keys()\r
- mods.sort()\r
- print "?", name, "imported from", ', '.join(mods)\r
- # Print modules that may be missing, but then again, maybe not...\r
- if maybe:\r
- print\r
- print "Submodules that appear to be missing, but could also be",\r
- print "global names in the parent package:"\r
- for name in maybe:\r
- mods = self.badmodules[name].keys()\r
- mods.sort()\r
- print "?", name, "imported from", ', '.join(mods)\r
-\r
- def any_missing(self):\r
- """Return a list of modules that appear to be missing. Use\r
- any_missing_maybe() if you want to know which modules are\r
- certain to be missing, and which *may* be missing.\r
- """\r
- missing, maybe = self.any_missing_maybe()\r
- return missing + maybe\r
-\r
- def any_missing_maybe(self):\r
- """Return two lists, one with modules that are certainly missing\r
- and one with modules that *may* be missing. The latter names could\r
- either be submodules *or* just global names in the package.\r
-\r
- The reason it can't always be determined is that it's impossible to\r
- tell which names are imported when "from module import *" is done\r
- with an extension module, short of actually importing it.\r
- """\r
- missing = []\r
- maybe = []\r
- for name in self.badmodules:\r
- if name in self.excludes:\r
- continue\r
- i = name.rfind(".")\r
- if i < 0:\r
- missing.append(name)\r
- continue\r
- subname = name[i+1:]\r
- pkgname = name[:i]\r
- pkg = self.modules.get(pkgname)\r
- if pkg is not None:\r
- if pkgname in self.badmodules[name]:\r
- # The package tried to import this module itself and\r
- # failed. It's definitely missing.\r
- missing.append(name)\r
- elif subname in pkg.globalnames:\r
- # It's a global in the package: definitely not missing.\r
- pass\r
- elif pkg.starimports:\r
- # It could be missing, but the package did an "import *"\r
- # from a non-Python module, so we simply can't be sure.\r
- maybe.append(name)\r
- else:\r
- # It's not a global in the package, the package didn't\r
- # do funny star imports, it's very likely to be missing.\r
- # The symbol could be inserted into the package from the\r
- # outside, but since that's not good style we simply list\r
- # it missing.\r
- missing.append(name)\r
- else:\r
- missing.append(name)\r
- missing.sort()\r
- maybe.sort()\r
- return missing, maybe\r
-\r
- def replace_paths_in_code(self, co):\r
- new_filename = original_filename = os.path.normpath(co.co_filename)\r
- for f, r in self.replace_paths:\r
- if original_filename.startswith(f):\r
- new_filename = r + original_filename[len(f):]\r
- break\r
-\r
- if self.debug and original_filename not in self.processed_paths:\r
- if new_filename != original_filename:\r
- self.msgout(2, "co_filename %r changed to %r" \\r
- % (original_filename,new_filename,))\r
- else:\r
- self.msgout(2, "co_filename %r remains unchanged" \\r
- % (original_filename,))\r
- self.processed_paths.append(original_filename)\r
-\r
- consts = list(co.co_consts)\r
- for i in range(len(consts)):\r
- if isinstance(consts[i], type(co)):\r
- consts[i] = self.replace_paths_in_code(consts[i])\r
-\r
- return types.CodeType(co.co_argcount, co.co_nlocals, co.co_stacksize,\r
- co.co_flags, co.co_code, tuple(consts), co.co_names,\r
- co.co_varnames, new_filename, co.co_name,\r
- co.co_firstlineno, co.co_lnotab,\r
- co.co_freevars, co.co_cellvars)\r
-\r
-\r
-def test():\r
- # Parse command line\r
- import getopt\r
- try:\r
- opts, args = getopt.getopt(sys.argv[1:], "dmp:qx:")\r
- except getopt.error, msg:\r
- print msg\r
- return\r
-\r
- # Process options\r
- debug = 1\r
- domods = 0\r
- addpath = []\r
- exclude = []\r
- for o, a in opts:\r
- if o == '-d':\r
- debug = debug + 1\r
- if o == '-m':\r
- domods = 1\r
- if o == '-p':\r
- addpath = addpath + a.split(os.pathsep)\r
- if o == '-q':\r
- debug = 0\r
- if o == '-x':\r
- exclude.append(a)\r
-\r
- # Provide default arguments\r
- if not args:\r
- script = "hello.py"\r
- else:\r
- script = args[0]\r
-\r
- # Set the path based on sys.path and the script directory\r
- path = sys.path[:]\r
- path[0] = os.path.dirname(script)\r
- path = addpath + path\r
- if debug > 1:\r
- print "path:"\r
- for item in path:\r
- print " ", repr(item)\r
-\r
- # Create the module finder and turn its crank\r
- mf = ModuleFinder(path, debug, exclude)\r
- for arg in args[1:]:\r
- if arg == '-m':\r
- domods = 1\r
- continue\r
- if domods:\r
- if arg[-2:] == '.*':\r
- mf.import_hook(arg[:-2], None, ["*"])\r
- else:\r
- mf.import_hook(arg)\r
- else:\r
- mf.load_file(arg)\r
- mf.run_script(script)\r
- mf.report()\r
- return mf # for -i debugging\r
-\r
-\r
-if __name__ == '__main__':\r
- try:\r
- mf = test()\r
- except KeyboardInterrupt:\r
- print "\n[interrupt]"\r