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