|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2020 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing an import hook patching modules to support debugging. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import importlib |
|
12 |
|
13 from QProcessExtension import patchQProcess |
|
14 from SubprocessExtension import patchSubprocess |
|
15 from MultiprocessingExtension import patchMultiprocessing |
|
16 |
|
17 |
|
18 class ModuleLoader: |
|
19 """ |
|
20 Class implementing an import hook patching modules to support debugging. |
|
21 """ |
|
22 def __init__(self, debugClient): |
|
23 """ |
|
24 Constructor |
|
25 |
|
26 @param debugClient reference to the debug client object |
|
27 @type DebugClient |
|
28 """ |
|
29 self.__dbgClient = debugClient |
|
30 |
|
31 self.__enableImportHooks = set() |
|
32 |
|
33 # reset already imported thread module to apply hooks at next import |
|
34 for moduleName in ("thread", "_thread", "threading"): |
|
35 if moduleName in sys.modules: |
|
36 del sys.modules[moduleName] |
|
37 |
|
38 self.__modulesToPatch = ( |
|
39 '_thread', 'threading', |
|
40 'greenlet', |
|
41 'subprocess', |
|
42 'multiprocessing', |
|
43 'PyQt5.QtCore', |
|
44 'PyQt6.QtCore', |
|
45 'PySide2.QtCore', |
|
46 'PySide6.QtCore', |
|
47 ) |
|
48 |
|
49 sys.meta_path.insert(0, self) |
|
50 |
|
51 def __loadModule(self, fullname): |
|
52 """ |
|
53 Private method to load a module. |
|
54 |
|
55 @param fullname name of the module to be loaded |
|
56 @type str |
|
57 @return reference to the loaded module |
|
58 @rtype module |
|
59 """ |
|
60 module = importlib.import_module(fullname) |
|
61 sys.modules[fullname] = module |
|
62 self.__enableImportHooks.remove(fullname) |
|
63 ## Add hook for _thread.start_new_thread |
|
64 if ( |
|
65 fullname == '_thread' and |
|
66 not hasattr(module, 'eric7_patched') |
|
67 ): |
|
68 module.eric7_patched = True |
|
69 self.__dbgClient.patchPyThread(module) |
|
70 |
|
71 ## Add hook for threading.run() |
|
72 elif ( |
|
73 fullname == "threading" and |
|
74 not hasattr(module, 'eric7_patched') |
|
75 ): |
|
76 module.eric7_patched = True |
|
77 self.__dbgClient.patchPyThreading(module) |
|
78 |
|
79 ## greenlet support |
|
80 elif ( |
|
81 fullname == 'greenlet' and |
|
82 not hasattr(module, 'eric7_patched') |
|
83 ): |
|
84 if self.__dbgClient.patchGreenlet(module): |
|
85 module.eric7_patched = True |
|
86 |
|
87 ## Add hook for subprocess.Popen() |
|
88 elif ( |
|
89 fullname == 'subprocess' and |
|
90 not hasattr(module, 'eric7_patched') |
|
91 ): |
|
92 module.eric7_patched = True |
|
93 patchSubprocess(module, self.__dbgClient) |
|
94 |
|
95 ## Add hook for multiprocessing.Process |
|
96 elif ( |
|
97 fullname == 'multiprocessing' and |
|
98 not hasattr(module, 'eric7_patched') |
|
99 ): |
|
100 module.eric7_patched = True |
|
101 patchMultiprocessing(module, self.__dbgClient) |
|
102 |
|
103 ## Add hook for *.QThread and *.QProcess |
|
104 elif ( |
|
105 fullname in ('PyQt5.QtCore', 'PyQt6.QtCore', |
|
106 'PySide2.QtCore', 'PySide6.QtCore') and |
|
107 not hasattr(module, 'eric7_patched') |
|
108 ): |
|
109 module.eric7_patched = True |
|
110 self.__dbgClient.patchQThread(module) |
|
111 patchQProcess(module, self.__dbgClient) |
|
112 |
|
113 return module |
|
114 |
|
115 def find_spec(self, fullname, path, target=None): |
|
116 """ |
|
117 Public method returning the module spec. |
|
118 |
|
119 @param fullname name of the module to be loaded |
|
120 @type str |
|
121 @param path path to resolve the module name |
|
122 @type str |
|
123 @param target module object to use for a more educated guess |
|
124 about what spec to return |
|
125 @type module |
|
126 @return module spec object pointing to the module loader |
|
127 @rtype ModuleSpec |
|
128 """ |
|
129 if fullname in sys.modules or self.__dbgClient.debugging is False: |
|
130 return None |
|
131 |
|
132 if ( |
|
133 fullname in self.__modulesToPatch and |
|
134 fullname not in self.__enableImportHooks |
|
135 ): |
|
136 # Disable hook to be able to import original module |
|
137 self.__enableImportHooks.add(fullname) |
|
138 return importlib.machinery.ModuleSpec(fullname, self) |
|
139 |
|
140 return None |
|
141 |
|
142 def create_module(self, spec): |
|
143 """ |
|
144 Public method to create a module based on the passed in spec. |
|
145 |
|
146 @param spec module spec object for loading the module |
|
147 @type ModuleSpec |
|
148 @return created and patched module |
|
149 @rtype module |
|
150 """ |
|
151 return self.__loadModule(spec.name) |
|
152 |
|
153 def exec_module(self, module): |
|
154 """ |
|
155 Public method to execute the created module. |
|
156 |
|
157 @param module module to be executed |
|
158 @type module |
|
159 """ |
|
160 pass |