@@ -243,70 +243,8 @@ def _cpu_count() -> int:
243
243
return 1
244
244
245
245
246
- if multiprocessing is not None :
247
-
248
- class ChildLinter (multiprocessing .Process ):
249
- def run (self ):
250
- # pylint: disable=no-member, unbalanced-tuple-unpacking
251
- tasks_queue , results_queue , self ._config = self ._args
252
-
253
- self ._config ["jobs" ] = 1 # Child does not parallelize any further.
254
- self ._python3_porting_mode = self ._config .pop ("python3_porting_mode" , None )
255
- self ._plugins = self ._config .pop ("plugins" , None )
256
-
257
- # Run linter for received files/modules.
258
- for file_or_module in iter (tasks_queue .get , "STOP" ):
259
- try :
260
- result = self ._run_linter (file_or_module [0 ])
261
- results_queue .put (result )
262
- except Exception as ex :
263
- print (
264
- "internal error with sending report for module %s"
265
- % file_or_module ,
266
- file = sys .stderr ,
267
- )
268
- print (ex , file = sys .stderr )
269
- results_queue .put ({})
270
-
271
- def _run_linter (self , file_or_module ):
272
- linter = PyLinter ()
273
-
274
- # Register standard checkers.
275
- linter .load_default_plugins ()
276
- # Load command line plugins.
277
- if self ._plugins :
278
- linter .load_plugin_modules (self ._plugins )
279
-
280
- linter .load_configuration_from_config (self ._config )
281
-
282
- # Load plugin specific configuration
283
- linter .load_plugin_configuration ()
284
-
285
- linter .set_reporter (reporters .CollectingReporter ())
286
-
287
- # Enable the Python 3 checker mode. This option is
288
- # passed down from the parent linter up to here, since
289
- # the Python 3 porting flag belongs to the Run class,
290
- # instead of the Linter class.
291
- if self ._python3_porting_mode :
292
- linter .python3_porting_mode ()
293
-
294
- # Run the checks.
295
- linter .check (file_or_module )
296
-
297
- msgs = [_get_new_args (m ) for m in linter .reporter .messages ]
298
- return (
299
- file_or_module ,
300
- linter .file_state .base_name ,
301
- linter .current_name ,
302
- msgs ,
303
- linter .stats ,
304
- linter .msg_status ,
305
- )
306
-
307
-
308
246
# pylint: disable=too-many-instance-attributes
309
- class PyLinter (
247
+ class PyLinter ( # pylint: disable=too-many-public-methods
310
248
config .OptionsManagerMixIn ,
311
249
MessagesHandlerMixIn ,
312
250
reporters .ReportsHandlerMixIn ,
@@ -971,16 +909,20 @@ def should_analyze_file(modname, path, is_argument=False):
971
909
972
910
# pylint: enable=unused-argument
973
911
974
- def check (self , files_or_modules ):
975
- """main checking entry: check a list of files or modules from their
976
- name.
977
- """
912
+ def initialize (self ):
978
913
# initialize msgs_state now that all messages have been registered into
979
914
# the store
980
915
for msg in self .msgs_store .messages :
981
916
if not msg .may_be_emitted ():
982
917
self ._msgs_state [msg .msgid ] = False
983
918
919
+ def check (self , files_or_modules ):
920
+ """main checking entry: check a list of files or modules from their
921
+ name.
922
+ """
923
+
924
+ self .initialize ()
925
+
984
926
if not isinstance (files_or_modules , (list , tuple )):
985
927
files_or_modules = (files_or_modules ,)
986
928
@@ -998,100 +940,21 @@ def check(self, files_or_modules):
998
940
elif self .config .jobs == 1 :
999
941
self ._check_files (self .get_ast , self ._iterate_file_descrs (files_or_modules ))
1000
942
else :
1001
- self ._parallel_check (files_or_modules )
1002
-
1003
- def _get_jobs_config (self ):
1004
- child_config = collections .OrderedDict ()
1005
- filter_options = {"long-help" }
1006
- filter_options .update ((opt_name for opt_name , _ in self ._external_opts ))
1007
- for opt_providers in self ._all_options .values ():
1008
- for optname , optdict , val in opt_providers .options_and_values ():
1009
- if optdict .get ("deprecated" ):
1010
- continue
1011
-
1012
- if optname not in filter_options :
1013
- child_config [optname ] = utils ._format_option_value (optdict , val )
1014
- child_config ["python3_porting_mode" ] = self ._python3_porting_mode
1015
- child_config ["plugins" ] = self ._dynamic_plugins
1016
- return child_config
1017
-
1018
- def _parallel_task (self , files_or_modules ):
1019
- # Prepare configuration for child linters.
1020
- child_config = self ._get_jobs_config ()
1021
-
1022
- children = []
1023
- manager = multiprocessing .Manager ()
1024
- tasks_queue = manager .Queue ()
1025
- results_queue = manager .Queue ()
1026
-
1027
- # Send files to child linters.
1028
- expanded_files = []
1029
- for descr in self ._expand_files (files_or_modules ):
1030
- modname , filepath , is_arg = descr ["name" ], descr ["path" ], descr ["isarg" ]
1031
- if self .should_analyze_file (modname , filepath , is_argument = is_arg ):
1032
- expanded_files .append (descr )
1033
-
1034
- # do not start more jobs than needed
1035
- for _ in range (min (self .config .jobs , len (expanded_files ))):
1036
- child_linter = ChildLinter (args = (tasks_queue , results_queue , child_config ))
1037
- child_linter .start ()
1038
- children .append (child_linter )
1039
-
1040
- for files_or_module in expanded_files :
1041
- path = files_or_module ["path" ]
1042
- tasks_queue .put ([path ])
1043
-
1044
- # collect results from child linters
1045
- failed = False
1046
- for _ in expanded_files :
1047
- try :
1048
- result = results_queue .get ()
1049
- except Exception as ex :
1050
- print (
1051
- "internal error while receiving results from child linter" ,
1052
- file = sys .stderr ,
1053
- )
1054
- print (ex , file = sys .stderr )
1055
- failed = True
1056
- break
1057
- yield result
1058
-
1059
- # Stop child linters and wait for their completion.
1060
- for _ in range (self .config .jobs ):
1061
- tasks_queue .put ("STOP" )
1062
- for child in children :
1063
- child .join ()
1064
-
1065
- if failed :
1066
- print ("Error occurred, stopping the linter." , file = sys .stderr )
1067
- sys .exit (32 )
1068
-
1069
- def _parallel_check (self , files_or_modules ):
1070
- # Reset stats.
1071
- self .open ()
1072
-
1073
- all_stats = []
1074
- module = None
1075
- for result in self ._parallel_task (files_or_modules ):
1076
- if not result :
1077
- continue
1078
- (_ , self .file_state .base_name , module , messages , stats , msg_status ) = result
1079
-
1080
- for msg in messages :
1081
- msg = Message (* msg )
1082
- self .set_current_module (module )
1083
- self .reporter .handle_message (msg )
943
+ check_parallel (
944
+ self , self .config .jobs , self ._iterate_file_descrs (files_or_modules )
945
+ )
1084
946
1085
- all_stats . append ( stats )
1086
- self . msg_status |= msg_status
947
+ def check_single_file ( self , name , filepath , modname ):
948
+ """Check single file
1087
949
1088
- self .stats = _merge_stats (all_stats )
1089
- self .current_name = module
950
+ The arguments are the same that are documented in _check_files
1090
951
1091
- # Insert stats data to local checkers.
1092
- for checker in self .get_checkers ():
1093
- if checker is not self :
1094
- checker .stats = self .stats
952
+ The initialize() method should be called before calling this method
953
+ """
954
+ with self ._astroid_module_checker () as check_astroid_module :
955
+ self ._check_file (
956
+ self .get_ast , check_astroid_module , name , filepath , modname
957
+ )
1095
958
1096
959
def _check_files (self , get_ast , file_descrs ):
1097
960
"""Check all files from file_descrs
@@ -1313,6 +1176,73 @@ def _report_evaluation(self):
1313
1176
self .reporter .display_reports (sect )
1314
1177
1315
1178
1179
+ def check_parallel (linter , jobs , files ):
1180
+ """Use the given linter to lint the files with given amount of workers (jobs)
1181
+ """
1182
+ original_reporter = linter .reporter
1183
+
1184
+ # Configure linter on the parent process side for the workers
1185
+ linter .set_reporter (reporters .CollectingReporter ())
1186
+ linter .open ()
1187
+
1188
+ # The linter is inherited by all the pool's workers, i.e. the linter
1189
+ # is identical to the linter object here. This is requirde so that
1190
+ # a custom PyLinter object (inherited from PyLinter) can be used.
1191
+ # See https://github.com/PyCQA/prospector/issues/320
1192
+ with multiprocessing .Pool (
1193
+ jobs , initializer = _worker_initialize , initargs = [linter ]
1194
+ ) as pool :
1195
+ # ..and now when the workers have inherited the linter, the actual reporter
1196
+ # can be set back here on the parent process so that results get stored into
1197
+ # correct reporter
1198
+ linter .set_reporter (original_reporter )
1199
+
1200
+ all_stats = []
1201
+
1202
+ for module , messages , stats , msg_status in pool .imap_unordered (
1203
+ _worker_check_single_file , files
1204
+ ):
1205
+ linter .set_current_module (module )
1206
+ for msg in messages :
1207
+ msg = Message (* msg )
1208
+ linter .reporter .handle_message (msg )
1209
+
1210
+ all_stats .append (stats )
1211
+ linter .msg_status |= msg_status
1212
+
1213
+ linter .stats = _merge_stats (all_stats )
1214
+
1215
+ # Insert stats data to local checkers.
1216
+ for checker in linter .get_checkers ():
1217
+ if checker is not linter :
1218
+ checker .stats = linter .stats
1219
+
1220
+
1221
+ # PyLinter object used by worker processes when checking files using multiprocessing
1222
+ # should only be used by the worker processes
1223
+ _worker_linter = None
1224
+
1225
+
1226
+ def _worker_initialize (linter ):
1227
+ global _worker_linter # pylint: disable=global-statement
1228
+ _worker_linter = linter
1229
+
1230
+
1231
+ def _worker_check_single_file (file_item ):
1232
+ name , filepath , modname = file_item
1233
+
1234
+ _worker_linter .open ()
1235
+ _worker_linter .check_single_file (name , filepath , modname )
1236
+
1237
+ msgs = [_get_new_args (m ) for m in _worker_linter .reporter .messages ]
1238
+ return (
1239
+ _worker_linter .current_name ,
1240
+ msgs ,
1241
+ _worker_linter .stats ,
1242
+ _worker_linter .msg_status ,
1243
+ )
1244
+
1245
+
1316
1246
# some reporting functions ####################################################
1317
1247
1318
1248
0 commit comments