123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- """
- modulegraph.find_modules - High-level module dependency finding interface
- =========================================================================
- History
- ........
- Originally (loosely) based on code in py2exe's build_exe.py by Thomas Heller.
- """
- import sys
- import os
- import imp
- import warnings
- import pkgutil
- from . import modulegraph
- from .modulegraph import Alias, Script, Extension
- from .util import imp_find_module
- __all__ = [
- 'find_modules', 'parse_mf_results'
- ]
- _PLATFORM_MODULES = {'posix', 'nt', 'os2', 'mac', 'ce', 'riscos'}
- def get_implies():
- result = {
- # imports done from builtin modules in C code
- # (untrackable by modulegraph)
- "_curses": ["curses"],
- "posix": ["resource"],
- "gc": ["time"],
- "time": ["_strptime"],
- "datetime": ["time"],
- "MacOS": ["macresource"],
- "cPickle": ["copy_reg", "cStringIO"],
- "parser": ["copy_reg"],
- "codecs": ["encodings"],
- "cStringIO": ["copy_reg"],
- "_sre": ["copy", "string", "sre"],
- "zipimport": ["zlib"],
- # Python 3.2:
- "_datetime": ["time", "_strptime"],
- "_json": ["json.decoder"],
- "_pickle": ["codecs", "copyreg", "_compat_pickle"],
- "_posixsubprocess": ["gc"],
- "_ssl": ["socket"],
- # Python 3.3:
- "_elementtree": ["copy", "xml.etree.ElementPath"],
- # mactoolboxglue can do a bunch more of these
- # that are far harder to predict, these should be tracked
- # manually for now.
- # this isn't C, but it uses __import__
- "anydbm": ["dbhash", "gdbm", "dbm", "dumbdbm", "whichdb"],
- # package aliases
- "wxPython.wx": Alias('wx'),
- }
- if sys.version_info[0] == 3:
- result["_sre"] = ["copy", "re"]
- result["parser"] = ["copyreg"]
- # _frozen_importlib is part of the interpreter itself
- result["_frozen_importlib"] = None
- if sys.version_info[0] == 2 and sys.version_info[1] >= 5:
- result.update({
- "email.base64MIME": Alias("email.base64mime"),
- "email.Charset": Alias("email.charset"),
- "email.Encoders": Alias("email.encoders"),
- "email.Errors": Alias("email.errors"),
- "email.Feedparser": Alias("email.feedParser"),
- "email.Generator": Alias("email.generator"),
- "email.Header": Alias("email.header"),
- "email.Iterators": Alias("email.iterators"),
- "email.Message": Alias("email.message"),
- "email.Parser": Alias("email.parser"),
- "email.quopriMIME": Alias("email.quoprimime"),
- "email.Utils": Alias("email.utils"),
- "email.MIMEAudio": Alias("email.mime.audio"),
- "email.MIMEBase": Alias("email.mime.base"),
- "email.MIMEImage": Alias("email.mime.image"),
- "email.MIMEMessage": Alias("email.mime.message"),
- "email.MIMEMultipart": Alias("email.mime.multipart"),
- "email.MIMENonMultipart": Alias("email.mime.nonmultipart"),
- "email.MIMEText": Alias("email.mime.text"),
- })
- if sys.version_info[:2] >= (2, 5):
- result["_elementtree"] = ["pyexpat"]
- import xml.etree
- for _, module_name, is_package in pkgutil.iter_modules(xml.etree.__path__):
- if not is_package:
- result["_elementtree"].append("xml.etree.%s" % (module_name,))
- if sys.version_info[:2] >= (2, 6):
- result['future_builtins'] = ['itertools']
- # os.path is an alias for a platform specific submodule,
- # ensure that the graph shows this.
- result['os.path'] = Alias(os.path.__name__)
- return result
- def parse_mf_results(mf):
- """
- Return two lists: the first one contains the python files in the graph,
- the second the C extensions.
- :param mf: a :class:`modulegraph.modulegraph.ModuleGraph` instance
- """
- # Retrieve modules from modulegraph
- py_files = []
- extensions = []
- for item in mf.iter_graph():
- # There may be __main__ modules (from mf.run_script), but
- # we don't need it in the zipfile we build.
- if item.identifier == "__main__":
- continue
- src = item.filename
- if src and src != '-':
- if isinstance(item, Script):
- # Scripts are python files
- py_files.append(item)
- elif isinstance(item, Extension):
- extensions.append(item)
- else:
- py_files.append(item)
- # sort on the file names, the output is nicer to read
- py_files.sort(key=lambda v: v.filename)
- extensions.sort(key=lambda v: v.filename)
- return py_files, extensions
- def plat_prepare(includes, packages, excludes):
- # used by Python itself
- includes.update(["warnings", "unicodedata", "weakref"])
- if not sys.platform.startswith('irix'):
- excludes.update([
- 'AL',
- 'sgi',
- 'vms_lib',
- ])
- if sys.platform not in ('mac', 'darwin'):
- # XXX - this doesn't look nearly complete
- excludes.update([
- 'Audio_mac',
- 'Carbon.File',
- 'Carbon.Folder',
- 'Carbon.Folders',
- 'EasyDialogs',
- 'MacOS',
- 'macfs',
- 'macostools',
- '_scproxy',
- ])
- if not sys.platform == 'win32':
- # only win32
- excludes.update([
- 'nturl2path',
- 'win32api',
- 'win32con',
- 'win32ctypes',
- 'win32event',
- 'win32evtlogutil',
- 'win32evtlog',
- 'win32file',
- 'win32gui',
- 'win32pipe',
- 'win32process',
- 'win32security',
- 'pywintypes',
- 'winsound',
- 'win32',
- '_winreg',
- '_winapi',
- 'msvcrt',
- 'winreg',
- '_subprocess',
- ])
- if not sys.platform == 'riscos':
- excludes.update([
- 'riscosenviron',
- 'rourl2path',
- ])
- if not sys.platform == 'dos' or sys.platform.startswith('ms-dos'):
- excludes.update([
- 'dos',
- ])
- if not sys.platform == 'os2emx':
- excludes.update([
- '_emx_link',
- ])
- excludes.update(_PLATFORM_MODULES - set(sys.builtin_module_names))
- # Carbon.Res depends on this, but the module hasn't been present
- # for a while...
- excludes.add('OverrideFrom23')
- excludes.add('OverrideFrom23._Res')
- # import trickery in the dummy_threading module (stdlib)
- excludes.add('_dummy_threading')
- try:
- imp_find_module('poll')
- except ImportError:
- excludes.update([
- 'poll',
- ])
- def find_needed_modules(
- mf=None, scripts=(), includes=(), packages=(), warn=warnings.warn):
- if mf is None:
- mf = modulegraph.ModuleGraph()
- # feed Modulefinder with everything, and return it.
- for path in scripts:
- mf.add_script(path)
- for mod in includes:
- try:
- if mod[-2:] == '.*':
- mf.import_hook(mod[:-2], None, ['*'])
- else:
- mf.import_hook(mod)
- except ImportError:
- warn("No module named %s" % (mod,))
- for f in packages:
- # If modulegraph has seen a reference to the package, then
- # we prefer to believe that (imp_find_module doesn't seem to locate
- # sub-packages)
- m = mf.find_node(f)
- if m is not None:
- path = m.packagepath[0]
- else:
- # Find path of package
- # TODO: use imp_find_module_or_importer
- try:
- path = imp_find_module(f, mf.path)[1]
- except ImportError:
- warn("No package named %s" % f)
- continue
- # walk the path to find subdirs containing __init__.py files
- # scan the results (directory of __init__.py files)
- # first trim the path (of the head package),
- # then convert directory name in package name,
- # finally push into modulegraph.
- # FIXME:
- # 1) Needs to be adjusted for namespace packages in python 3.3
- # 2) Code is fairly dodgy and needs better tests
- for (dirpath, dirnames, filenames) in os.walk(path):
- if '__init__.py' in filenames and dirpath.startswith(path):
- package = f + '.' + dirpath[len(path)+1:].replace(os.sep, '.')
- if package.endswith('.'):
- package = package[:-1]
- m = mf.import_hook(package, None, ["*"])
- else:
- # Exclude subtrees that aren't packages
- dirnames[:] = []
- return mf
- #
- # resource constants
- #
- PY_SUFFIXES = ['.py', '.pyw', '.pyo', '.pyc']
- C_SUFFIXES = [
- _triple[0] for _triple in imp.get_suffixes()
- if _triple[2] == imp.C_EXTENSION
- ]
- #
- # side-effects
- #
- def _replacePackages():
- REPLACEPACKAGES = {
- '_xmlplus': 'xml',
- }
- for k, v in REPLACEPACKAGES.items():
- modulegraph.replacePackage(k, v)
- _replacePackages()
- def find_modules(
- scripts=(), includes=(), packages=(), excludes=(), path=None, debug=0):
- """
- High-level interface, takes iterables for:
- scripts, includes, packages, excludes
- And returns a :class:`modulegraph.modulegraph.ModuleGraph` instance,
- python_files, and extensions
- python_files is a list of pure python dependencies as modulegraph.Module
- objects, extensions is a list of platform-specific C extension dependencies
- as modulegraph.Module objects
- """
- scripts = set(scripts)
- includes = set(includes)
- packages = set(packages)
- excludes = set(excludes)
- plat_prepare(includes, packages, excludes)
- mf = modulegraph.ModuleGraph(
- path=path,
- excludes=(excludes - includes),
- implies=get_implies(),
- debug=debug,
- )
- find_needed_modules(mf, scripts, includes, packages)
- return mf
|