zmc
2023-10-12 ed135d79df12a2466b52dae1a82326941211dcc9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"""
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