1#----------------------------------------------------------------------------- 2# Copyright (c) 2005-2019, PyInstaller Development Team. 3# 4# Distributed under the terms of the GNU General Public License with exception 5# for distributing bootloader. 6# 7# The full license is in the file COPYING.txt, distributed with this software. 8#----------------------------------------------------------------------------- 9 10""" 11Define a modified ModuleGraph that can return its contents as 12a TOC and in other ways act like the old ImpTracker. 13TODO: This class, along with TOC and Tree should be in a separate module. 14 15For reference, the ModuleGraph node types and their contents: 16 17 nodetype identifier filename 18 19 Script full path to .py full path to .py 20 SourceModule basename full path to .py 21 BuiltinModule basename None 22 CompiledModule basename full path to .pyc 23 Extension basename full path to .so 24 MissingModule basename None 25 Package basename full path to __init__.py 26 packagepath is ['path to package'] 27 globalnames is set of global names __init__.py defines 28 29The main extension here over ModuleGraph is a method to extract nodes 30from the flattened graph and return them as a TOC, or added to a TOC. 31Other added methods look up nodes by identifier and return facts 32about them, replacing what the old ImpTracker list could do. 33""" 34 35from __future__ import print_function 36 37import os 38import re 39import sys 40import traceback 41 42from .. import HOMEPATH, configure 43from .. import log as logging 44from ..log import INFO, DEBUG, TRACE 45from ..building.datastruct import TOC 46from ..building.imphook import HooksCache 47from ..building.imphookapi import PreSafeImportModuleAPI, PreFindModulePathAPI 48from ..compat import importlib_load_source, is_py2, PY3_BASE_MODULES,\ 49 PURE_PYTHON_MODULE_TYPES, BINARY_MODULE_TYPES, VALID_MODULE_TYPES, \ 50 BAD_MODULE_TYPES, MODULE_TYPES_TO_TOC_DICT 51from ..lib.modulegraph.find_modules import get_implies 52from ..lib.modulegraph.modulegraph import ModuleGraph 53from ..utils.hooks import collect_submodules, is_package 54from ..utils.misc import load_py_data_struct 55 56logger = logging.getLogger(__name__) 57 58 59class PyiModuleGraph(ModuleGraph): 60 """ 61 Directed graph whose nodes represent modules and edges represent 62 dependencies between these modules. 63 64 This high-level subclass wraps the lower-level `ModuleGraph` class with 65 support for graph and runtime hooks. While each instance of `ModuleGraph` 66 represents a set of disconnected trees, each instance of this class *only* 67 represents a single connected tree whose root node is the Python script 68 originally passed by the user on the command line. For that reason, while 69 there may (and typically do) exist more than one `ModuleGraph` instance, 70 there typically exists only a singleton instance of this class. 71 72 Attributes 73 ---------- 74 _hooks_pre_find_module_path : HooksCache 75 Dictionary mapping the fully-qualified names of all modules with 76 pre-find module path hooks to the absolute paths of such hooks. See the 77 the `_find_module_path()` method for details. 78 _hooks_pre_safe_import_module : HooksCache 79 Dictionary mapping the fully-qualified names of all modules with 80 pre-safe import module hooks to the absolute paths of such hooks. See 81 the `_safe_import_module()` method for details. 82 _user_hook_dirs : list 83 List of the absolute paths of all directories containing user-defined 84 hooks for the current application. 85 """ 86 87 # Note: these levels are completely arbitrary and may be adjusted if needed. 88 LOG_LEVEL_MAPPING = {0: INFO, 1: DEBUG, 2: TRACE, 3: TRACE, 4: TRACE} 89 90 def __init__(self, pyi_homepath, user_hook_dirs=None, *args, **kwargs): 91 super(PyiModuleGraph, self).__init__(*args, **kwargs) 92 # Homepath to the place where is PyInstaller located. 93 self._homepath = pyi_homepath 94 # modulegraph Node for the main python script that is analyzed 95 # by PyInstaller. 96 self._top_script_node = None 97 98 # Absolute paths of all user-defined hook directories. 99 self._user_hook_dirs = \ 100 user_hook_dirs if user_hook_dirs is not None else [] 101 102 # Hook-specific lookup tables, defined after defining "_user_hook_dirs". 103 logger.info('Initializing module graph hooks...') 104 self._hooks_pre_safe_import_module = self._cache_hooks('pre_safe_import_module') 105 self._hooks_pre_find_module_path = self._cache_hooks('pre_find_module_path') 106 self._available_rthooks = load_py_data_struct( 107 os.path.join(self._homepath, 'PyInstaller', 'loader', 'rthooks.dat') 108 ) 109 110 @staticmethod 111 def _findCaller(*args, **kwargs): 112 # Used to add an additional stack-frame above logger.findCaller. 113 # findCaller expects the caller to be three stack-frames above itself. 114 return logger.findCaller(*args, **kwargs) 115 116 def msg(self, level, s, *args): 117 """ 118 Print a debug message with the given level. 119 120 1. Map the msg log level to a logger log level. 121 2. Generate the message format (the same format as ModuleGraph) 122 3. Find the caller, which findCaller expects three stack-frames above 123 itself: 124 [3] caller -> [2] msg (here) -> [1] _findCaller -> [0] logger.findCaller 125 4. Create a logRecord with the caller's information. 126 5. Handle the logRecord. 127 """ 128 try: 129 level = self.LOG_LEVEL_MAPPING[level] 130 except KeyError: 131 return 132 if not logger.isEnabledFor(level): 133 return 134 135 msg = "%s %s" % (s, ' '.join(map(repr, args))) 136 137 if is_py2: 138 # Python 2 does not have 'sinfo' 139 try: 140 fn, lno, func = self._findCaller() 141 except ValueError: # pragma: no cover 142 fn, lno, func = "(unknown file)", 0, "(unknown function)" 143 record = logger.makeRecord( 144 logger.name, level, fn, lno, msg, [], None, func, None) 145 else: 146 try: 147 fn, lno, func, sinfo = self._findCaller() 148 except ValueError: # pragma: no cover 149 fn, lno, func, sinfo = "(unknown file)", 0, "(unknown function)", None 150 record = logger.makeRecord( 151 logger.name, level, fn, lno, msg, [], None, func, None, sinfo) 152 153 logger.handle(record) 154 155 # Set logging methods so that the stack is correctly detected. 156 msgin = msg 157 msgout = msg 158 159 def _cache_hooks(self, hook_type): 160 """ 161 Get a cache of all hooks of the passed type. 162 163 The cache will include all official hooks defined by the PyInstaller 164 codebase _and_ all unofficial hooks defined for the current application. 165 166 Parameters 167 ---------- 168 hook_type : str 169 Type of hooks to be cached, equivalent to the basename of the 170 subpackage of the `PyInstaller.hooks` package containing such hooks 171 (e.g., `post_create_package` for post-create package hooks). 172 """ 173 # Absolute path of this type hook package's directory. 174 system_hook_dir = configure.get_importhooks_dir(hook_type) 175 176 # Cache of such hooks. 177 # logger.debug("Caching system %s hook dir %r" % (hook_type, system_hook_dir)) 178 hooks_cache = HooksCache(system_hook_dir) 179 for user_hook_dir in self._user_hook_dirs: 180 # Absolute path of the user-defined subdirectory of this hook type. 181 user_hook_type_dir = os.path.join(user_hook_dir, hook_type) 182 183 # If this directory exists, cache all hooks in this directory. 184 if os.path.isdir(user_hook_type_dir): 185 # logger.debug("Caching user %s hook dir %r" % (hook_type, hooks_user_dir)) 186 hooks_cache.add_custom_paths([user_hook_type_dir]) 187 188 return hooks_cache 189 190 def run_script(self, pathname, caller=None): 191 """ 192 Wrap the parent's 'run_script' method and create graph from the first 193 script in the analysis, and save its node to use as the "caller" node 194 for all others. This gives a connected graph rather than a collection 195 of unrelated trees, 196 """ 197 if self._top_script_node is None: 198 nodes_without_parent = [x for x in self.flatten()] 199 # Remember the node for the first script. 200 try: 201 self._top_script_node = super(PyiModuleGraph, self).run_script(pathname) 202 except SyntaxError as e: 203 print("\nSyntax error in", pathname, file=sys.stderr) 204 formatted_lines = traceback.format_exc().splitlines(True) 205 print(*formatted_lines[-4:], file=sys.stderr) 206 raise SystemExit(1) 207 # Create references from top_script to current modules in graph. 208 # These modules without parents are dependencies that are necessary 209 # for base_library.zip. 210 for node in nodes_without_parent: 211 self.createReference(self._top_script_node, node) 212 # Return top-level script node. 213 return self._top_script_node 214 else: 215 if not caller: 216 # Defaults to as any additional script is called from the top-level 217 # script. 218 caller = self._top_script_node 219 return super(PyiModuleGraph, self).run_script(pathname, caller=caller) 220 221 def _safe_import_module(self, module_basename, module_name, parent_package): 222 """ 223 Create a new graph node for the module with the passed name under the 224 parent package signified by the passed graph node. 225 226 This method wraps the superclass method with support for pre-import 227 module hooks. If such a hook exists for this module (e.g., a script 228 `PyInstaller.hooks.hook-{module_name}` containing a function 229 `pre_safe_import_module()`), that hook will be run _before_ the 230 superclass method is called. 231 232 Pre-Safe-Import-Hooks are performed just *prior* to importing 233 the module. When running the hook, the modules parent package 234 has already been imported and ti's `__path__` is set up. But 235 the module is just about to be imported. 236 237 See the superclass method for description of parameters and 238 return value. 239 """ 240 # If this module has pre-safe import module hooks, run these first. 241 if module_name in self._hooks_pre_safe_import_module: 242 # For the absolute path of each such hook... 243 for hook_file in self._hooks_pre_safe_import_module[module_name]: 244 # Dynamically import this hook as a fabricated module. 245 logger.info('Processing pre-safe import module hook %s', module_name) 246 hook_module_name = 'PyInstaller_hooks_pre_safe_import_module_' + module_name.replace('.', '_') 247 hook_module = importlib_load_source(hook_module_name, hook_file) 248 249 # Object communicating changes made by this hook back to us. 250 hook_api = PreSafeImportModuleAPI( 251 module_graph=self, 252 module_basename=module_basename, 253 module_name=module_name, 254 parent_package=parent_package, 255 ) 256 257 # Run this hook, passed this object. 258 if not hasattr(hook_module, 'pre_safe_import_module'): 259 raise NameError('pre_safe_import_module() function not defined by hook %r.' % hook_file) 260 hook_module.pre_safe_import_module(hook_api) 261 262 # Respect method call changes requested by this hook. 263 module_basename = hook_api.module_basename 264 module_name = hook_api.module_name 265 266 # Prevent subsequent calls from rerunning these hooks. 267 del self._hooks_pre_safe_import_module[module_name] 268 269 # Call the superclass method. 270 return super(PyiModuleGraph, self)._safe_import_module( 271 module_basename, module_name, parent_package) 272 273 def _find_module_path(self, fullname, module_name, search_dirs): 274 """ 275 Get a 3-tuple detailing the physical location of the module with the 276 passed name if that module exists _or_ raise `ImportError` otherwise. 277 278 This method wraps the superclass method with support for pre-find module 279 path hooks. If such a hook exists for this module (e.g., a script 280 `PyInstaller.hooks.hook-{module_name}` containing a function 281 `pre_find_module_path()`), that hook will be run _before_ the 282 superclass method is called. 283 284 See superclass method for parameter and return value descriptions. 285 """ 286 # If this module has pre-find module path hooks, run these first. 287 if fullname in self._hooks_pre_find_module_path: 288 # For the absolute path of each such hook... 289 for hook_file in self._hooks_pre_find_module_path[fullname]: 290 # Dynamically import this hook as a fabricated module. 291 logger.info('Processing pre-find module path hook %s', fullname) 292 hook_fullname = 'PyInstaller_hooks_pre_find_module_path_' + fullname.replace('.', '_') 293 hook_module = importlib_load_source(hook_fullname, hook_file) 294 295 # Object communicating changes made by this hook back to us. 296 hook_api = PreFindModulePathAPI( 297 module_graph=self, 298 module_name=fullname, 299 search_dirs=search_dirs, 300 ) 301 302 # Run this hook, passed this object. 303 if not hasattr(hook_module, 'pre_find_module_path'): 304 raise NameError('pre_find_module_path() function not defined by hook %r.' % hook_file) 305 hook_module.pre_find_module_path(hook_api) 306 307 # Respect method call changes requested by this hook. 308 search_dirs = hook_api.search_dirs 309 310 # Prevent subsequent calls from rerunning these hooks. 311 del self._hooks_pre_find_module_path[fullname] 312 313 # Call the superclass method. 314 return super(PyiModuleGraph, self)._find_module_path( 315 fullname, module_name, search_dirs) 316 317 def get_code_objects(self): 318 """ 319 Get code objects from ModuleGraph for pure Pyhton modules. This allows 320 to avoid writing .pyc/pyo files to hdd at later stage. 321 322 :return: Dict with module name and code object. 323 """ 324 code_dict = {} 325 mod_types = PURE_PYTHON_MODULE_TYPES 326 for node in self.flatten(start=self._top_script_node): 327 # TODO This is terrible. To allow subclassing, types should never be 328 # directly compared. Use isinstance() instead, which is safer, 329 # simpler, and accepts sets. Most other calls to type() in the 330 # codebase should also be refactored to call isinstance() instead. 331 332 # get node type e.g. Script 333 mg_type = type(node).__name__ 334 if mg_type in mod_types: 335 if node.code: 336 code_dict[node.identifier] = node.code 337 return code_dict 338 339 def _make_toc(self, typecode=None, existing_TOC=None): 340 """ 341 Return the name, path and type of selected nodes as a TOC, or appended 342 to a TOC. The selection is via a list of PyInstaller TOC typecodes. 343 If that list is empty we return the complete flattened graph as a TOC 344 with the ModuleGraph note types in place of typecodes -- meant for 345 debugging only. Normally we return ModuleGraph nodes whose types map 346 to the requested PyInstaller typecode(s) as indicated in the MODULE_TYPES_TO_TOC_DICT. 347 348 We use the ModuleGraph (really, ObjectGraph) flatten() method to 349 scan all the nodes. This is patterned after ModuleGraph.report(). 350 """ 351 # Construct regular expression for matching modules that should be 352 # excluded because they are bundled in base_library.zip. 353 # 354 # This expression matches the base module name, optionally followed by 355 # a period and then any number of characters. This matches the module name and 356 # the fully qualified names of any of its submodules. 357 regex_str = '(' + '|'.join(PY3_BASE_MODULES) + r')(\.|$)' 358 module_filter = re.compile(regex_str) 359 360 result = existing_TOC or TOC() 361 for node in self.flatten(start=self._top_script_node): 362 # TODO This is terrible. Everything in Python has a type. It's 363 # nonsensical to even speak of "nodes [that] are not typed." How 364 # would that even occur? After all, even "None" has a type! (It's 365 # "NoneType", for the curious.) Remove this, please. 366 367 # Skip modules that are in base_library.zip. 368 if not is_py2 and module_filter.match(node.identifier): 369 continue 370 371 # get node type e.g. Script 372 mg_type = type(node).__name__ 373 assert mg_type is not None 374 375 if typecode and not (mg_type in typecode): 376 # Type is not a to be selected one, skip this one 377 continue 378 # Extract the identifier and a path if any. 379 if mg_type == 'Script': 380 # for Script nodes only, identifier is a whole path 381 (name, ext) = os.path.splitext(node.filename) 382 name = os.path.basename(name) 383 else: 384 name = node.identifier 385 path = node.filename if node.filename is not None else '' 386 # Ensure name is really 'str'. Module graph might return 387 # object type 'modulegraph.Alias' which inherits fromm 'str'. 388 # But 'marshal.dumps()' function is able to marshal only 'str'. 389 # Otherwise on Windows PyInstaller might fail with message like: 390 # 391 # ValueError: unmarshallable object 392 name = str(name) 393 # Translate to the corresponding TOC typecode. 394 toc_type = MODULE_TYPES_TO_TOC_DICT[mg_type] 395 # TOC.append the data. This checks for a pre-existing name 396 # and skips it if it exists. 397 result.append((name, path, toc_type)) 398 return result 399 400 def make_pure_toc(self): 401 """ 402 Return all pure Python modules formatted as TOC. 403 """ 404 # PyInstaller should handle special module types without code object. 405 return self._make_toc(PURE_PYTHON_MODULE_TYPES) 406 407 def make_binaries_toc(self, existing_toc): 408 """ 409 Return all binary Python modules formatted as TOC. 410 """ 411 return self._make_toc(BINARY_MODULE_TYPES, existing_toc) 412 413 def make_missing_toc(self): 414 """ 415 Return all MISSING Python modules formatted as TOC. 416 """ 417 return self._make_toc(BAD_MODULE_TYPES) 418 419 def nodes_to_toc(self, node_list, existing_TOC=None): 420 """ 421 Given a list of nodes, create a TOC representing those nodes. 422 This is mainly used to initialize a TOC of scripts with the 423 ones that are runtime hooks. The process is almost the same as 424 _make_toc(), but the caller guarantees the nodes are 425 valid, so minimal checking. 426 """ 427 result = existing_TOC or TOC() 428 for node in node_list: 429 mg_type = type(node).__name__ 430 toc_type = MODULE_TYPES_TO_TOC_DICT[mg_type] 431 if mg_type == "Script" : 432 (name, ext) = os.path.splitext(node.filename) 433 name = os.path.basename(name) 434 else: 435 name = node.identifier 436 path = node.filename if node.filename is not None else '' 437 result.append( (name, path, toc_type) ) 438 return result 439 440 # Return true if the named item is in the graph as a BuiltinModule node. 441 # The passed name is a basename. 442 def is_a_builtin(self, name) : 443 node = self.findNode(name) 444 if node is None: 445 return False 446 return type(node).__name__ == 'BuiltinModule' 447 448 def get_importers(self, name): 449 """List all modules importing the module with the passed name. 450 451 Returns a list of (identifier, DependencyIinfo)-tuples. If the names 452 module has not yet been imported, this method returns an empty list. 453 454 Parameters 455 ---------- 456 name : str 457 Fully-qualified name of the module to be examined. 458 459 Returns 460 ---------- 461 list 462 List of (fully-qualified names, DependencyIinfo)-tuples of all 463 modules importing the module with the passed fully-qualified name. 464 465 """ 466 def get_importer_edge_data(importer): 467 edge = self.graph.edge_by_node(importer, name) 468 # edge might be None in case an AliasModule was added. 469 if edge is not None: 470 return self.graph.edge_data(edge) 471 472 node = self.findNode(name) 473 if node is None : return [] 474 _, importers = self.get_edges(node) 475 importers = (importer.identifier 476 for importer in importers 477 if importer is not None) 478 return [(importer, get_importer_edge_data(importer)) 479 for importer in importers] 480 481 # TODO create class from this function. 482 def analyze_runtime_hooks(self, custom_runhooks): 483 """ 484 Analyze custom run-time hooks and run-time hooks implied by found modules. 485 486 :return : list of Graph nodes. 487 """ 488 rthooks_nodes = [] 489 logger.info('Analyzing run-time hooks ...') 490 # Process custom runtime hooks (from --runtime-hook options). 491 # The runtime hooks are order dependent. First hooks in the list 492 # are executed first. Put their graph nodes at the head of the 493 # priority_scripts list Pyinstaller-defined rthooks and 494 # thus they are executed first. 495 if custom_runhooks: 496 for hook_file in custom_runhooks: 497 logger.info("Including custom run-time hook %r", hook_file) 498 hook_file = os.path.abspath(hook_file) 499 # Not using "try" here because the path is supposed to 500 # exist, if it does not, the raised error will explain. 501 rthooks_nodes.append(self.run_script(hook_file)) 502 503 # Find runtime hooks that are implied by packages already imported. 504 # Get a temporary TOC listing all the scripts and packages graphed 505 # so far. Assuming that runtime hooks apply only to modules and packages. 506 temp_toc = self._make_toc(VALID_MODULE_TYPES) 507 for (mod_name, path, typecode) in temp_toc: 508 # Look if there is any run-time hook for given module. 509 if mod_name in self._available_rthooks: 510 # There could be several run-time hooks for a module. 511 for hook in self._available_rthooks[mod_name]: 512 logger.info("Including run-time hook %r", hook) 513 path = os.path.join(self._homepath, 'PyInstaller', 'loader', 'rthooks', hook) 514 rthooks_nodes.append(self.run_script(path)) 515 516 return rthooks_nodes 517 518 def add_hiddenimports(self, module_list): 519 """ 520 Add hidden imports that are either supplied as CLI option --hidden-import=MODULENAME 521 or as dependencies from some PyInstaller features when enabled (e.g. crypto feature). 522 """ 523 # Analyze the script's hidden imports (named on the command line) 524 for modnm in module_list: 525 logger.debug('Hidden import: %s' % modnm) 526 if self.findNode(modnm) is not None: 527 logger.debug('Hidden import %r already found', modnm) 528 continue 529 logger.info("Analyzing hidden import %r", modnm) 530 # ModuleGraph throws ImportError if import not found 531 try : 532 node = self.import_hook(modnm) 533 except ImportError: 534 logger.error("Hidden import %r not found", modnm) 535 536 537 def get_co_using_ctypes(self): 538 """ 539 Find modules that imports Python module 'ctypes'. 540 541 Modules that imports 'ctypes' probably load a dll that might be required 542 for bundling with the executable. The usual way to load a DLL is using: 543 ctypes.CDLL('libname') 544 ctypes.cdll.LoadLibrary('libname') 545 546 :return: Code objects that might be scanned for module dependencies. 547 """ 548 co_dict = {} 549 pure_python_module_types = PURE_PYTHON_MODULE_TYPES | {'Script',} 550 node = self.findNode('ctypes') 551 if node: 552 referers = self.getReferers(node) 553 for r in referers: 554 r_ident = r.identifier 555 # Ensure that modulegraph objects has attribute 'code'. 556 if type(r).__name__ in pure_python_module_types: 557 if r_ident == 'ctypes' or r_ident.startswith('ctypes.'): 558 # Skip modules of 'ctypes' package. 559 continue 560 co_dict[r.identifier] = r.code 561 return co_dict 562 563 564# TODO: A little odd. Couldn't we just push this functionality into the 565# PyiModuleGraph.__init__() constructor and then construct PyiModuleGraph 566# objects directly? 567def initialize_modgraph(excludes=(), user_hook_dirs=None): 568 """ 569 Create the module graph and, for Python 3, analyze dependencies for 570 `base_library.zip` (which remain the same for every executable). 571 572 This function might appear weird but is necessary for speeding up 573 test runtime because it allows caching basic ModuleGraph object that 574 gets created for 'base_library.zip'. 575 576 Parameters 577 ---------- 578 excludes : list 579 List of the fully-qualified names of all modules to be "excluded" and 580 hence _not_ frozen into the executable. 581 user_hook_dirs : list 582 List of the absolute paths of all directories containing user-defined 583 hooks for the current application or `None` if no such directories were 584 specified. 585 586 Returns 587 ---------- 588 PyiModuleGraph 589 Module graph with core dependencies. 590 """ 591 logger.info('Initializing module dependency graph...') 592 593 # Construct the initial module graph by analyzing all import statements. 594 graph = PyiModuleGraph( 595 HOMEPATH, 596 excludes=excludes, 597 # get_implies() are hidden imports known by modulgraph. 598 implies=get_implies(), 599 user_hook_dirs=user_hook_dirs, 600 ) 601 602 if not is_py2: 603 logger.info('Analyzing base_library.zip ...') 604 required_mods = [] 605 # Collect submodules from required modules in base_library.zip. 606 for m in PY3_BASE_MODULES: 607 if is_package(m): 608 required_mods += collect_submodules(m) 609 else: 610 required_mods.append(m) 611 # Initialize ModuleGraph. 612 for m in required_mods: 613 graph.import_hook(m) 614 return graph 615 616 617def get_bootstrap_modules(): 618 """ 619 Get TOC with the bootstrapping modules and their dependencies. 620 :return: TOC with modules 621 """ 622 # Import 'struct' modules to get real paths to module file names. 623 mod_struct = __import__('struct') 624 # Basic modules necessary for the bootstrap process. 625 loader_mods = [] 626 loaderpath = os.path.join(HOMEPATH, 'PyInstaller', 'loader') 627 # On some platforms (Windows, Debian/Ubuntu) '_struct' and zlib modules are 628 # built-in modules (linked statically) and thus does not have attribute __file__. 629 # 'struct' module is required for reading Python bytecode from executable. 630 # 'zlib' is required to decompress this bytecode. 631 for mod_name in ['_struct', 'zlib']: 632 mod = __import__(mod_name) # C extension. 633 if hasattr(mod, '__file__'): 634 loader_mods.append((mod_name, os.path.abspath(mod.__file__), 'EXTENSION')) 635 # NOTE:These modules should be kept simple without any complicated dependencies. 636 loader_mods +=[ 637 ('struct', os.path.abspath(mod_struct.__file__), 'PYMODULE'), 638 ('pyimod01_os_path', os.path.join(loaderpath, 'pyimod01_os_path.pyc'), 'PYMODULE'), 639 ('pyimod02_archive', os.path.join(loaderpath, 'pyimod02_archive.pyc'), 'PYMODULE'), 640 ('pyimod03_importers', os.path.join(loaderpath, 'pyimod03_importers.pyc'), 'PYMODULE'), 641 ('pyiboot01_bootstrap', os.path.join(loaderpath, 'pyiboot01_bootstrap.py'), 'PYSOURCE'), 642 ] 643 # TODO Why is here the call to TOC()? 644 toc = TOC(loader_mods) 645 return toc 646