1 """Find modules used by a script, using introspection."""
2 # This module should be kept compatible with Python 2.2, see PEP 291.
4 from __future__
import generators
13 if hasattr(sys
.__stdout
__, "newlines"):
14 READ_MODE
= "U" # universal line endings
16 # remain compatible with Python < 2.3
19 LOAD_CONST
= chr(dis
.opname
.index('LOAD_CONST'))
20 IMPORT_NAME
= chr(dis
.opname
.index('IMPORT_NAME'))
21 STORE_NAME
= chr(dis
.opname
.index('STORE_NAME'))
22 STORE_GLOBAL
= chr(dis
.opname
.index('STORE_GLOBAL'))
23 STORE_OPS
= [STORE_NAME
, STORE_GLOBAL
]
24 HAVE_ARGUMENT
= chr(dis
.HAVE_ARGUMENT
)
26 # Modulefinder does a good job at simulating Python's, but it can not
27 # handle __path__ modifications packages make at runtime. Therefore there
28 # is a mechanism whereby you can register extra paths in this map for a
29 # package, and it will be honored.
31 # Note this is a mapping is lists of paths.
35 def AddPackagePath(packagename
, path
):
36 paths
= packagePathMap
.get(packagename
, [])
38 packagePathMap
[packagename
] = paths
40 replacePackageMap
= {}
42 # This ReplacePackage mechanism allows modulefinder to work around the
43 # way the _xmlplus package injects itself under the name "xml" into
44 # sys.modules at runtime by calling ReplacePackage("_xmlplus", "xml")
45 # before running ModuleFinder.
47 def ReplacePackage(oldname
, newname
):
48 replacePackageMap
[oldname
] = newname
53 def __init__(self
, name
, file=None, path
=None):
58 # The set of global names that are assigned to in the module.
59 # This includes those names imported through starimports of
62 # The set of starimports this module did that could not be
63 # resolved, ie. a starimport from a non-Python module.
67 s
= "Module(%r" % (self
.__name
__,)
68 if self
.__file
__ is not None:
69 s
= s
+ ", %r" % (self
.__file
__,)
70 if self
.__path
__ is not None:
71 s
= s
+ ", %r" % (self
.__path
__,)
77 def __init__(self
, path
=None, debug
=0, excludes
=[], replace_paths
=[]):
85 self
.excludes
= excludes
86 self
.replace_paths
= replace_paths
87 self
.processed_paths
= [] # Used in debugging only
89 def msg(self
, level
, str, *args
):
90 if level
<= self
.debug
:
91 for i
in range(self
.indent
):
98 def msgin(self
, *args
):
100 if level
<= self
.debug
:
101 self
.indent
= self
.indent
+ 1
104 def msgout(self
, *args
):
106 if level
<= self
.debug
:
107 self
.indent
= self
.indent
- 1
110 def run_script(self
, pathname
):
111 self
.msg(2, "run_script", pathname
)
112 fp
= open(pathname
, READ_MODE
)
113 stuff
= ("", "r", imp
.PY_SOURCE
)
114 self
.load_module('__main__', fp
, pathname
, stuff
)
116 def load_file(self
, pathname
):
117 dir, name
= os
.path
.split(pathname
)
118 name
, ext
= os
.path
.splitext(name
)
119 fp
= open(pathname
, READ_MODE
)
120 stuff
= (ext
, "r", imp
.PY_SOURCE
)
121 self
.load_module(name
, fp
, pathname
, stuff
)
123 def import_hook(self
, name
, caller
=None, fromlist
=None, level
=-1):
124 self
.msg(3, "import_hook", name
, caller
, fromlist
, level
)
125 parent
= self
.determine_parent(caller
, level
=level
)
126 q
, tail
= self
.find_head_package(parent
, name
)
127 m
= self
.load_tail(q
, tail
)
131 self
.ensure_fromlist(m
, fromlist
)
134 def determine_parent(self
, caller
, level
=-1):
135 self
.msgin(4, "determine_parent", caller
, level
)
136 if not caller
or level
== 0:
137 self
.msgout(4, "determine_parent -> None")
139 pname
= caller
.__name
__
140 if level
>= 1: # relative import
144 parent
= self
.modules
[pname
]
145 assert parent
is caller
146 self
.msgout(4, "determine_parent ->", parent
)
148 if pname
.count(".") < level
:
149 raise ImportError, "relative importpath too deep"
150 pname
= ".".join(pname
.split(".")[:-level
])
151 parent
= self
.modules
[pname
]
152 self
.msgout(4, "determine_parent ->", parent
)
155 parent
= self
.modules
[pname
]
156 assert caller
is parent
157 self
.msgout(4, "determine_parent ->", parent
)
162 parent
= self
.modules
[pname
]
163 assert parent
.__name
__ == pname
164 self
.msgout(4, "determine_parent ->", parent
)
166 self
.msgout(4, "determine_parent -> None")
169 def find_head_package(self
, parent
, name
):
170 self
.msgin(4, "find_head_package", parent
, name
)
179 qname
= "%s.%s" % (parent
.__name
__, head
)
182 q
= self
.import_module(head
, qname
, parent
)
184 self
.msgout(4, "find_head_package ->", (q
, tail
))
189 q
= self
.import_module(head
, qname
, parent
)
191 self
.msgout(4, "find_head_package ->", (q
, tail
))
193 self
.msgout(4, "raise ImportError: No module named", qname
)
194 raise ImportError, "No module named " + qname
196 def load_tail(self
, q
, tail
):
197 self
.msgin(4, "load_tail", q
, tail
)
201 if i
< 0: i
= len(tail
)
202 head
, tail
= tail
[:i
], tail
[i
+1:]
203 mname
= "%s.%s" % (m
.__name
__, head
)
204 m
= self
.import_module(head
, mname
, m
)
206 self
.msgout(4, "raise ImportError: No module named", mname
)
207 raise ImportError, "No module named " + mname
208 self
.msgout(4, "load_tail ->", m
)
211 def ensure_fromlist(self
, m
, fromlist
, recursive
=0):
212 self
.msg(4, "ensure_fromlist", m
, fromlist
, recursive
)
216 all
= self
.find_all_submodules(m
)
218 self
.ensure_fromlist(m
, all
, 1)
219 elif not hasattr(m
, sub
):
220 subname
= "%s.%s" % (m
.__name
__, sub
)
221 submod
= self
.import_module(sub
, subname
, m
)
223 raise ImportError, "No module named " + subname
225 def find_all_submodules(self
, m
):
229 # 'suffixes' used to be a list hardcoded to [".py", ".pyc", ".pyo"].
230 # But we must also collect Python extension modules - although
231 # we cannot separate normal dlls from Python extensions.
233 for triple
in imp
.get_suffixes():
234 suffixes
.append(triple
[0])
235 for dir in m
.__path
__:
237 names
= os
.listdir(dir)
239 self
.msg(2, "can't list directory", dir)
243 for suff
in suffixes
:
245 if name
[-n
:] == suff
:
248 if mod
and mod
!= "__init__":
250 return modules
.keys()
252 def import_module(self
, partname
, fqname
, parent
):
253 self
.msgin(3, "import_module", partname
, fqname
, parent
)
255 m
= self
.modules
[fqname
]
259 self
.msgout(3, "import_module ->", m
)
261 if fqname
in self
.badmodules
:
262 self
.msgout(3, "import_module -> None")
264 if parent
and parent
.__path
__ is None:
265 self
.msgout(3, "import_module -> None")
268 fp
, pathname
, stuff
= self
.find_module(partname
,
269 parent
and parent
.__path
__, parent
)
271 self
.msgout(3, "import_module ->", None)
274 m
= self
.load_module(fqname
, fp
, pathname
, stuff
)
278 setattr(parent
, partname
, m
)
279 self
.msgout(3, "import_module ->", m
)
282 def load_module(self
, fqname
, fp
, pathname
, file_info
):
283 suffix
, mode
, type = file_info
284 self
.msgin(2, "load_module", fqname
, fp
and "fp", pathname
)
285 if type == imp
.PKG_DIRECTORY
:
286 m
= self
.load_package(fqname
, pathname
)
287 self
.msgout(2, "load_module ->", m
)
289 if type == imp
.PY_SOURCE
:
290 co
= compile(fp
.read()+'\n', pathname
, 'exec')
291 elif type == imp
.PY_COMPILED
:
292 if fp
.read(4) != imp
.get_magic():
293 self
.msgout(2, "raise ImportError: Bad magic number", pathname
)
294 raise ImportError, "Bad magic number in %s" % pathname
296 co
= marshal
.load(fp
)
299 m
= self
.add_module(fqname
)
300 m
.__file
__ = pathname
302 if self
.replace_paths
:
303 co
= self
.replace_paths_in_code(co
)
305 self
.scan_code(co
, m
)
306 self
.msgout(2, "load_module ->", m
)
309 def _add_badmodule(self
, name
, caller
):
310 if name
not in self
.badmodules
:
311 self
.badmodules
[name
] = {}
313 self
.badmodules
[name
][caller
.__name
__] = 1
315 self
.badmodules
[name
]["-"] = 1
317 def _safe_import_hook(self
, name
, caller
, fromlist
, level
=-1):
318 # wrapper for self.import_hook() that won't raise ImportError
319 if name
in self
.badmodules
:
320 self
._add
_badmodule
(name
, caller
)
323 self
.import_hook(name
, caller
, level
=level
)
324 except ImportError, msg
:
325 self
.msg(2, "ImportError:", str(msg
))
326 self
._add
_badmodule
(name
, caller
)
330 if sub
in self
.badmodules
:
331 self
._add
_badmodule
(sub
, caller
)
334 self
.import_hook(name
, caller
, [sub
], level
=level
)
335 except ImportError, msg
:
336 self
.msg(2, "ImportError:", str(msg
))
337 fullname
= name
+ "." + sub
338 self
._add
_badmodule
(fullname
, caller
)
340 def scan_opcodes(self
, co
,
341 unpack
= struct
.unpack
):
342 # Scan the code, and yield 'interesting' opcode combinations
343 # Version for Python 2.4 and older
346 consts
= co
.co_consts
350 oparg
, = unpack('<H', code
[1:3])
351 yield "store", (names
[oparg
],)
354 if c
== LOAD_CONST
and code
[3] == IMPORT_NAME
:
355 oparg_1
, oparg_2
= unpack('<xHxH', code
[:6])
356 yield "import", (consts
[oparg_1
], names
[oparg_2
])
359 if c
>= HAVE_ARGUMENT
:
364 def scan_opcodes_25(self
, co
,
365 unpack
= struct
.unpack
):
366 # Scan the code, and yield 'interesting' opcode combinations
367 # Python 2.5 version (has absolute and relative imports)
370 consts
= co
.co_consts
371 LOAD_LOAD_AND_IMPORT
= LOAD_CONST
+ LOAD_CONST
+ IMPORT_NAME
375 oparg
, = unpack('<H', code
[1:3])
376 yield "store", (names
[oparg
],)
379 if code
[:9:3] == LOAD_LOAD_AND_IMPORT
:
380 oparg_1
, oparg_2
, oparg_3
= unpack('<xHxHxH', code
[:9])
381 level
= consts
[oparg_1
]
382 if level
== -1: # normal import
383 yield "import", (consts
[oparg_2
], names
[oparg_3
])
384 elif level
== 0: # absolute import
385 yield "absolute_import", (consts
[oparg_2
], names
[oparg_3
])
386 else: # relative import
387 yield "relative_import", (level
, consts
[oparg_2
], names
[oparg_3
])
390 if c
>= HAVE_ARGUMENT
:
395 def scan_code(self
, co
, m
):
397 if sys
.version_info
>= (2, 5):
398 scanner
= self
.scan_opcodes_25
400 scanner
= self
.scan_opcodes
401 for what
, args
in scanner(co
):
404 m
.globalnames
[name
] = 1
405 elif what
in ("import", "absolute_import"):
406 fromlist
, name
= args
408 if fromlist
is not None:
411 fromlist
= [f
for f
in fromlist
if f
!= "*"]
412 if what
== "absolute_import": level
= 0
414 self
._safe
_import
_hook
(name
, m
, fromlist
, level
=level
)
416 # We've encountered an "import *". If it is a Python module,
417 # the code has already been parsed and we can suck out the
421 # At this point we don't know whether 'name' is a
422 # submodule of 'm' or a global module. Let's just try
423 # the full name first.
424 mm
= self
.modules
.get(m
.__name
__ + "." + name
)
426 mm
= self
.modules
.get(name
)
428 m
.globalnames
.update(mm
.globalnames
)
429 m
.starimports
.update(mm
.starimports
)
430 if mm
.__code
__ is None:
431 m
.starimports
[name
] = 1
433 m
.starimports
[name
] = 1
434 elif what
== "relative_import":
435 level
, fromlist
, name
= args
437 self
._safe
_import
_hook
(name
, m
, fromlist
, level
=level
)
439 parent
= self
.determine_parent(m
, level
=level
)
440 self
._safe
_import
_hook
(parent
.__name
__, None, fromlist
, level
=0)
442 # We don't expect anything else from the generator.
443 raise RuntimeError(what
)
445 for c
in co
.co_consts
:
446 if isinstance(c
, type(co
)):
449 def load_package(self
, fqname
, pathname
):
450 self
.msgin(2, "load_package", fqname
, pathname
)
451 newname
= replacePackageMap
.get(fqname
)
454 m
= self
.add_module(fqname
)
455 m
.__file
__ = pathname
456 m
.__path
__ = [pathname
]
458 # As per comment at top of file, simulate runtime __path__ additions.
459 m
.__path
__ = m
.__path
__ + packagePathMap
.get(fqname
, [])
461 fp
, buf
, stuff
= self
.find_module("__init__", m
.__path
__)
462 self
.load_module(fqname
, fp
, buf
, stuff
)
463 self
.msgout(2, "load_package ->", m
)
466 def add_module(self
, fqname
):
467 if fqname
in self
.modules
:
468 return self
.modules
[fqname
]
469 self
.modules
[fqname
] = m
= Module(fqname
)
472 def find_module(self
, name
, path
, parent
=None):
473 if parent
is not None:
474 # assert path is not None
475 fullname
= parent
.__name
__+'.'+name
478 if fullname
in self
.excludes
:
479 self
.msgout(3, "find_module -> Excluded", fullname
)
480 raise ImportError, name
483 if name
in sys
.builtin_module_names
:
484 return (None, None, ("", "", imp
.C_BUILTIN
))
487 return imp
.find_module(name
, path
)
490 """Print a report to stdout, listing the found modules with their
491 paths, as well as modules that are missing, or seem to be missing.
494 print " %-25s %s" % ("Name", "File")
495 print " %-25s %s" % ("----", "----")
496 # Print modules found
497 keys
= self
.modules
.keys()
500 m
= self
.modules
[key
]
505 print "%-25s" % key
, m
.__file
__ or ""
507 # Print missing modules
508 missing
, maybe
= self
.any_missing_maybe()
511 print "Missing modules:"
513 mods
= self
.badmodules
[name
].keys()
515 print "?", name
, "imported from", ', '.join(mods
)
516 # Print modules that may be missing, but then again, maybe not...
519 print "Submodules thay appear to be missing, but could also be",
520 print "global names in the parent package:"
522 mods
= self
.badmodules
[name
].keys()
524 print "?", name
, "imported from", ', '.join(mods
)
526 def any_missing(self
):
527 """Return a list of modules that appear to be missing. Use
528 any_missing_maybe() if you want to know which modules are
529 certain to be missing, and which *may* be missing.
531 missing
, maybe
= self
.any_missing_maybe()
532 return missing
+ maybe
534 def any_missing_maybe(self
):
535 """Return two lists, one with modules that are certainly missing
536 and one with modules that *may* be missing. The latter names could
537 either be submodules *or* just global names in the package.
539 The reason it can't always be determined is that it's impossible to
540 tell which names are imported when "from module import *" is done
541 with an extension module, short of actually importing it.
545 for name
in self
.badmodules
:
546 if name
in self
.excludes
:
554 pkg
= self
.modules
.get(pkgname
)
556 if pkgname
in self
.badmodules
[name
]:
557 # The package tried to import this module itself and
558 # failed. It's definitely missing.
560 elif subname
in pkg
.globalnames
:
561 # It's a global in the package: definitely not missing.
563 elif pkg
.starimports
:
564 # It could be missing, but the package did an "import *"
565 # from a non-Python module, so we simply can't be sure.
568 # It's not a global in the package, the package didn't
569 # do funny star imports, it's very likely to be missing.
570 # The symbol could be inserted into the package from the
571 # outside, but since that's not good style we simply list
578 return missing
, maybe
580 def replace_paths_in_code(self
, co
):
581 new_filename
= original_filename
= os
.path
.normpath(co
.co_filename
)
582 for f
, r
in self
.replace_paths
:
583 if original_filename
.startswith(f
):
584 new_filename
= r
+ original_filename
[len(f
):]
587 if self
.debug
and original_filename
not in self
.processed_paths
:
588 if new_filename
!= original_filename
:
589 self
.msgout(2, "co_filename %r changed to %r" \
590 % (original_filename
,new_filename
,))
592 self
.msgout(2, "co_filename %r remains unchanged" \
593 % (original_filename
,))
594 self
.processed_paths
.append(original_filename
)
596 consts
= list(co
.co_consts
)
597 for i
in range(len(consts
)):
598 if isinstance(consts
[i
], type(co
)):
599 consts
[i
] = self
.replace_paths_in_code(consts
[i
])
601 return types
.CodeType(co
.co_argcount
, co
.co_nlocals
, co
.co_stacksize
,
602 co
.co_flags
, co
.co_code
, tuple(consts
), co
.co_names
,
603 co
.co_varnames
, new_filename
, co
.co_name
,
604 co
.co_firstlineno
, co
.co_lnotab
,
605 co
.co_freevars
, co
.co_cellvars
)
612 opts
, args
= getopt
.getopt(sys
.argv
[1:], "dmp:qx:")
613 except getopt
.error
, msg
:
628 addpath
= addpath
+ a
.split(os
.pathsep
)
634 # Provide default arguments
640 # Set the path based on sys.path and the script directory
642 path
[0] = os
.path
.dirname(script
)
643 path
= addpath
+ path
647 print " ", repr(item
)
649 # Create the module finder and turn its crank
650 mf
= ModuleFinder(path
, debug
, exclude
)
657 mf
.import_hook(arg
[:-2], None, ["*"])
662 mf
.run_script(script
)
664 return mf
# for -i debugging
667 if __name__
== '__main__':
670 except KeyboardInterrupt:
671 print "\n[interrupt]"