1 """Find modules used by a script, using introspection."""
3 # This module should be kept compatible with Python 1.5.2, see PEP 291.
12 if hasattr(sys
.__stdout
__, "newlines"):
13 READ_MODE
= "U" # universal line endings
15 # remain compatible with Python < 2.3
18 LOAD_CONST
= dis
.opname
.index('LOAD_CONST')
19 IMPORT_NAME
= dis
.opname
.index('IMPORT_NAME')
20 STORE_NAME
= dis
.opname
.index('STORE_NAME')
21 STORE_GLOBAL
= dis
.opname
.index('STORE_GLOBAL')
22 STORE_OPS
= [STORE_NAME
, STORE_GLOBAL
]
24 # Modulefinder does a good job at simulating Python's, but it can not
25 # handle __path__ modifications packages make at runtime. Therefore there
26 # is a mechanism whereby you can register extra paths in this map for a
27 # package, and it will be honored.
29 # Note this is a mapping is lists of paths.
33 def AddPackagePath(packagename
, path
):
34 paths
= packagePathMap
.get(packagename
, [])
36 packagePathMap
[packagename
] = paths
38 replacePackageMap
= {}
40 # This ReplacePackage mechanism allows modulefinder to work around the
41 # way the _xmlplus package injects itself under the name "xml" into
42 # sys.modules at runtime by calling ReplacePackage("_xmlplus", "xml")
43 # before running ModuleFinder.
45 def ReplacePackage(oldname
, newname
):
46 replacePackageMap
[oldname
] = newname
51 def __init__(self
, name
, file=None, path
=None):
56 # The set of global names that are assigned to in the module.
57 # This includes those names imported through starimports of
60 # The set of starimports this module did that could not be
61 # resolved, ie. a starimport from a non-Python module.
65 s
= "Module(%s" % `self
.__name
__`
66 if self
.__file
__ is not None:
67 s
= s
+ ", %s" % `self
.__file
__`
68 if self
.__path
__ is not None:
69 s
= s
+ ", %s" % `self
.__path
__`
75 def __init__(self
, path
=None, debug
=0, excludes
=[], replace_paths
=[]):
83 self
.excludes
= excludes
84 self
.replace_paths
= replace_paths
85 self
.processed_paths
= [] # Used in debugging only
87 def msg(self
, level
, str, *args
):
88 if level
<= self
.debug
:
89 for i
in range(self
.indent
):
96 def msgin(self
, *args
):
98 if level
<= self
.debug
:
99 self
.indent
= self
.indent
+ 1
102 def msgout(self
, *args
):
104 if level
<= self
.debug
:
105 self
.indent
= self
.indent
- 1
108 def run_script(self
, pathname
):
109 self
.msg(2, "run_script", pathname
)
110 fp
= open(pathname
, READ_MODE
)
111 stuff
= ("", "r", imp
.PY_SOURCE
)
112 self
.load_module('__main__', fp
, pathname
, stuff
)
114 def load_file(self
, pathname
):
115 dir, name
= os
.path
.split(pathname
)
116 name
, ext
= os
.path
.splitext(name
)
117 fp
= open(pathname
, READ_MODE
)
118 stuff
= (ext
, "r", imp
.PY_SOURCE
)
119 self
.load_module(name
, fp
, pathname
, stuff
)
121 def import_hook(self
, name
, caller
=None, fromlist
=None):
122 self
.msg(3, "import_hook", name
, caller
, fromlist
)
123 parent
= self
.determine_parent(caller
)
124 q
, tail
= self
.find_head_package(parent
, name
)
125 m
= self
.load_tail(q
, tail
)
129 self
.ensure_fromlist(m
, fromlist
)
132 def determine_parent(self
, caller
):
133 self
.msgin(4, "determine_parent", caller
)
135 self
.msgout(4, "determine_parent -> None")
137 pname
= caller
.__name
__
139 parent
= self
.modules
[pname
]
140 assert caller
is parent
141 self
.msgout(4, "determine_parent ->", parent
)
146 parent
= self
.modules
[pname
]
147 assert parent
.__name
__ == pname
148 self
.msgout(4, "determine_parent ->", parent
)
150 self
.msgout(4, "determine_parent -> None")
153 def find_head_package(self
, parent
, name
):
154 self
.msgin(4, "find_head_package", parent
, name
)
163 qname
= "%s.%s" % (parent
.__name
__, head
)
166 q
= self
.import_module(head
, qname
, parent
)
168 self
.msgout(4, "find_head_package ->", (q
, tail
))
173 q
= self
.import_module(head
, qname
, parent
)
175 self
.msgout(4, "find_head_package ->", (q
, tail
))
177 self
.msgout(4, "raise ImportError: No module named", qname
)
178 raise ImportError, "No module named " + qname
180 def load_tail(self
, q
, tail
):
181 self
.msgin(4, "load_tail", q
, tail
)
185 if i
< 0: i
= len(tail
)
186 head
, tail
= tail
[:i
], tail
[i
+1:]
187 mname
= "%s.%s" % (m
.__name
__, head
)
188 m
= self
.import_module(head
, mname
, m
)
190 self
.msgout(4, "raise ImportError: No module named", mname
)
191 raise ImportError, "No module named " + mname
192 self
.msgout(4, "load_tail ->", m
)
195 def ensure_fromlist(self
, m
, fromlist
, recursive
=0):
196 self
.msg(4, "ensure_fromlist", m
, fromlist
, recursive
)
200 all
= self
.find_all_submodules(m
)
202 self
.ensure_fromlist(m
, all
, 1)
203 elif not hasattr(m
, sub
):
204 subname
= "%s.%s" % (m
.__name
__, sub
)
205 submod
= self
.import_module(sub
, subname
, m
)
207 raise ImportError, "No module named " + subname
209 def find_all_submodules(self
, m
):
213 suffixes
= [".py", ".pyc", ".pyo"]
214 for dir in m
.__path
__:
216 names
= os
.listdir(dir)
218 self
.msg(2, "can't list directory", dir)
222 for suff
in suffixes
:
224 if name
[-n
:] == suff
:
227 if mod
and mod
!= "__init__":
229 return modules
.keys()
231 def import_module(self
, partname
, fqname
, parent
):
232 self
.msgin(3, "import_module", partname
, fqname
, parent
)
234 m
= self
.modules
[fqname
]
238 self
.msgout(3, "import_module ->", m
)
240 if self
.badmodules
.has_key(fqname
):
241 self
.msgout(3, "import_module -> None")
244 fp
, pathname
, stuff
= self
.find_module(partname
,
245 parent
and parent
.__path
__, parent
)
247 self
.msgout(3, "import_module ->", None)
250 m
= self
.load_module(fqname
, fp
, pathname
, stuff
)
254 setattr(parent
, partname
, m
)
255 self
.msgout(3, "import_module ->", m
)
258 def load_module(self
, fqname
, fp
, pathname
, (suffix
, mode
, type)):
259 self
.msgin(2, "load_module", fqname
, fp
and "fp", pathname
)
260 if type == imp
.PKG_DIRECTORY
:
261 m
= self
.load_package(fqname
, pathname
)
262 self
.msgout(2, "load_module ->", m
)
264 if type == imp
.PY_SOURCE
:
265 co
= compile(fp
.read()+'\n', pathname
, 'exec')
266 elif type == imp
.PY_COMPILED
:
267 if fp
.read(4) != imp
.get_magic():
268 self
.msgout(2, "raise ImportError: Bad magic number", pathname
)
269 raise ImportError, "Bad magic number in %s" % pathname
271 co
= marshal
.load(fp
)
274 m
= self
.add_module(fqname
)
275 m
.__file
__ = pathname
277 if self
.replace_paths
:
278 co
= self
.replace_paths_in_code(co
)
280 self
.scan_code(co
, m
)
281 self
.msgout(2, "load_module ->", m
)
284 def _add_badmodule(self
, name
, caller
):
285 if name
not in self
.badmodules
:
286 self
.badmodules
[name
] = {}
287 self
.badmodules
[name
][caller
.__name
__] = 1
289 def _safe_import_hook(self
, name
, caller
, fromlist
):
290 # wrapper for self.import_hook() that won't raise ImportError
291 if name
in self
.badmodules
:
292 self
._add
_badmodule
(name
, caller
)
295 self
.import_hook(name
, caller
)
296 except ImportError, msg
:
297 self
.msg(2, "ImportError:", str(msg
))
298 self
._add
_badmodule
(name
, caller
)
302 if sub
in self
.badmodules
:
303 self
._add
_badmodule
(sub
, caller
)
306 self
.import_hook(name
, caller
, [sub
])
307 except ImportError, msg
:
308 self
.msg(2, "ImportError:", str(msg
))
309 fullname
= name
+ "." + sub
310 self
._add
_badmodule
(fullname
, caller
)
312 def scan_code(self
, co
, m
):
321 if op
>= dis
.HAVE_ARGUMENT
:
322 oparg
= ord(code
[i
]) + ord(code
[i
+1])*256
325 # An IMPORT_NAME is always preceded by a LOAD_CONST, it's
326 # a tuple of "from" names, or None for a regular import.
327 # The tuple may contain "*" for "from <mod> import *"
328 fromlist
= co
.co_consts
[oparg
]
329 elif op
== IMPORT_NAME
:
330 assert fromlist
is None or type(fromlist
) is tuple
331 name
= co
.co_names
[oparg
]
333 if fromlist
is not None:
336 fromlist
= [f
for f
in fromlist
if f
!= "*"]
337 self
._safe
_import
_hook
(name
, m
, fromlist
)
339 # We've encountered an "import *". If it is a Python module,
340 # the code has already been parsed and we can suck out the
344 # At this point we don't know whether 'name' is a
345 # submodule of 'm' or a global module. Let's just try
346 # the full name first.
347 mm
= self
.modules
.get(m
.__name
__ + "." + name
)
349 mm
= self
.modules
.get(name
)
351 m
.globalnames
.update(mm
.globalnames
)
352 m
.starimports
.update(mm
.starimports
)
353 if mm
.__code
__ is None:
354 m
.starimports
[name
] = 1
356 m
.starimports
[name
] = 1
357 elif op
in STORE_OPS
:
358 # keep track of all global names that are assigned to
359 name
= co
.co_names
[oparg
]
360 m
.globalnames
[name
] = 1
361 for c
in co
.co_consts
:
362 if isinstance(c
, type(co
)):
365 def load_package(self
, fqname
, pathname
):
366 self
.msgin(2, "load_package", fqname
, pathname
)
367 newname
= replacePackageMap
.get(fqname
)
370 m
= self
.add_module(fqname
)
371 m
.__file
__ = pathname
372 m
.__path
__ = [pathname
]
374 # As per comment at top of file, simulate runtime __path__ additions.
375 m
.__path
__ = m
.__path
__ + packagePathMap
.get(fqname
, [])
377 fp
, buf
, stuff
= self
.find_module("__init__", m
.__path
__)
378 self
.load_module(fqname
, fp
, buf
, stuff
)
379 self
.msgout(2, "load_package ->", m
)
382 def add_module(self
, fqname
):
383 if self
.modules
.has_key(fqname
):
384 return self
.modules
[fqname
]
385 self
.modules
[fqname
] = m
= Module(fqname
)
388 def find_module(self
, name
, path
, parent
=None):
389 if parent
is not None:
390 fullname
= parent
.__name
__+'.'+name
393 if fullname
in self
.excludes
:
394 self
.msgout(3, "find_module -> Excluded", fullname
)
395 raise ImportError, name
398 if name
in sys
.builtin_module_names
:
399 return (None, None, ("", "", imp
.C_BUILTIN
))
402 return imp
.find_module(name
, path
)
405 """Print a report to stdout, listing the found modules with their
406 paths, as well as modules that are missing, or seem to be missing.
409 print " %-25s %s" % ("Name", "File")
410 print " %-25s %s" % ("----", "----")
411 # Print modules found
412 keys
= self
.modules
.keys()
415 m
= self
.modules
[key
]
420 print "%-25s" % key
, m
.__file
__ or ""
422 # Print missing modules
423 missing
, maybe
= self
.any_missing_maybe()
426 print "Missing modules:"
428 mods
= self
.badmodules
[name
].keys()
430 print "?", name
, "imported from", ', '.join(mods
)
431 # Print modules that may be missing, but then again, maybe not...
434 print "Submodules thay appear to be missing, but could also be",
435 print "global names in the parent package:"
437 mods
= self
.badmodules
[name
].keys()
439 print "?", name
, "imported from", ', '.join(mods
)
441 def any_missing(self
):
442 """Return a list of modules that appear to be missing. Use
443 any_missing_maybe() if you want to know which modules are
444 certain to be missing, and which *may* be missing.
446 missing
, maybe
= self
.any_missing_maybe()
447 return missing
+ maybe
449 def any_missing_maybe(self
):
450 """Return two lists, one with modules that are certainly missing
451 and one with modules that *may* be missing. The latter names could
452 either be submodules *or* just global names in the package.
454 The reason it can't always be determined is that it's impossible to
455 tell which names are imported when "from module import *" is done
456 with an extension module, short of actually importing it.
460 for name
in self
.badmodules
:
461 if name
in self
.excludes
:
469 pkg
= self
.modules
.get(pkgname
)
471 if pkgname
in self
.badmodules
[name
]:
472 # The package tried to import this module itself and
473 # failed. It's definitely missing.
475 elif subname
in pkg
.globalnames
:
476 # It's a global in the package: definitely not missing.
478 elif pkg
.starimports
:
479 # It could be missing, but the package did an "import *"
480 # from a non-Python module, so we simply can't be sure.
483 # It's not a global in the package, the package didn't
484 # do funny star imports, it's very likely to be missing.
485 # The symbol could be inserted into the package from the
486 # outside, but since that's not good style we simply list
493 return missing
, maybe
495 def replace_paths_in_code(self
, co
):
496 new_filename
= original_filename
= os
.path
.normpath(co
.co_filename
)
497 for f
, r
in self
.replace_paths
:
498 if original_filename
.startswith(f
):
499 new_filename
= r
+ original_filename
[len(f
):]
502 if self
.debug
and original_filename
not in self
.processed_paths
:
503 if new_filename
!= original_filename
:
504 self
.msgout(2, "co_filename %r changed to %r" \
505 % (original_filename
,new_filename
,))
507 self
.msgout(2, "co_filename %r remains unchanged" \
508 % (original_filename
,))
509 self
.processed_paths
.append(original_filename
)
511 consts
= list(co
.co_consts
)
512 for i
in range(len(consts
)):
513 if isinstance(consts
[i
], type(co
)):
514 consts
[i
] = self
.replace_paths_in_code(consts
[i
])
516 return new
.code(co
.co_argcount
, co
.co_nlocals
, co
.co_stacksize
,
517 co
.co_flags
, co
.co_code
, tuple(consts
), co
.co_names
,
518 co
.co_varnames
, new_filename
, co
.co_name
,
519 co
.co_firstlineno
, co
.co_lnotab
,
520 co
.co_freevars
, co
.co_cellvars
)
527 opts
, args
= getopt
.getopt(sys
.argv
[1:], "dmp:qx:")
528 except getopt
.error
, msg
:
543 addpath
= addpath
+ a
.split(os
.pathsep
)
549 # Provide default arguments
555 # Set the path based on sys.path and the script directory
557 path
[0] = os
.path
.dirname(script
)
558 path
= addpath
+ path
564 # Create the module finder and turn its crank
565 mf
= ModuleFinder(path
, debug
, exclude
)
572 mf
.import_hook(arg
[:-2], None, ["*"])
577 mf
.run_script(script
)
579 return mf
# for -i debugging
582 if __name__
== '__main__':
585 except KeyboardInterrupt:
586 print "\n[interrupt]"