@@ -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 ,
@@ -323,6 +261,9 @@ class PyLinter(
323
261
IDE plugin developers: you may have to call
324
262
`astroid.builder.MANAGER.astroid_cache.clear()` across runs if you want
325
263
to ensure the latest code version is actually checked.
264
+
265
+ This class needs to support pickling for parallel linting to work. The exception
266
+ is reporter member; see check_parallel function for more details.
326
267
"""
327
268
328
269
__implements__ = (interfaces .ITokenChecker ,)
@@ -971,16 +912,20 @@ def should_analyze_file(modname, path, is_argument=False):
971
912
972
913
# pylint: enable=unused-argument
973
914
974
- def check (self , files_or_modules ):
975
- """main checking entry: check a list of files or modules from their
976
- name.
977
- """
915
+ def initialize (self ):
978
916
# initialize msgs_state now that all messages have been registered into
979
917
# the store
980
918
for msg in self .msgs_store .messages :
981
919
if not msg .may_be_emitted ():
982
920
self ._msgs_state [msg .msgid ] = False
983
921
922
+ def check (self , files_or_modules ):
923
+ """main checking entry: check a list of files or modules from their
924
+ name.
925
+ """
926
+
927
+ self .initialize ()
928
+
984
929
if not isinstance (files_or_modules , (list , tuple )):
985
930
files_or_modules = (files_or_modules ,)
986
931
@@ -998,100 +943,21 @@ def check(self, files_or_modules):
998
943
elif self .config .jobs == 1 :
999
944
self ._check_files (self .get_ast , self ._iterate_file_descrs (files_or_modules ))
1000
945
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 )
946
+ check_parallel (
947
+ self , self .config .jobs , self ._iterate_file_descrs (files_or_modules )
948
+ )
1084
949
1085
- all_stats . append ( stats )
1086
- self . msg_status |= msg_status
950
+ def check_single_file ( self , name , filepath , modname ):
951
+ """Check single file
1087
952
1088
- self .stats = _merge_stats (all_stats )
1089
- self .current_name = module
953
+ The arguments are the same that are documented in _check_files
1090
954
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
955
+ The initialize() method should be called before calling this method
956
+ """
957
+ with self ._astroid_module_checker () as check_astroid_module :
958
+ self ._check_file (
959
+ self .get_ast , check_astroid_module , name , filepath , modname
960
+ )
1095
961
1096
962
def _check_files (self , get_ast , file_descrs ):
1097
963
"""Check all files from file_descrs
@@ -1326,6 +1192,78 @@ def _report_evaluation(self):
1326
1192
self .reporter .display_reports (sect )
1327
1193
1328
1194
1195
+ def check_parallel (linter , jobs , files ):
1196
+ """Use the given linter to lint the files with given amount of workers (jobs)
1197
+ """
1198
+ # The reporter does not need to be passed to worker processess, i.e. the reporter does
1199
+ # not need to be pickleable
1200
+ original_reporter = linter .reporter
1201
+ linter .reporter = None
1202
+
1203
+ # The linter is inherited by all the pool's workers, i.e. the linter
1204
+ # is identical to the linter object here. This is requirde so that
1205
+ # a custom PyLinter object (inherited from PyLinter) can be used.
1206
+ # See https://github.com/PyCQA/prospector/issues/320
1207
+ with multiprocessing .Pool (
1208
+ jobs , initializer = _worker_initialize , initargs = [linter ]
1209
+ ) as pool :
1210
+ # ..and now when the workers have inherited the linter, the actual reporter
1211
+ # can be set back here on the parent process so that results get stored into
1212
+ # correct reporter
1213
+ linter .set_reporter (original_reporter )
1214
+ linter .open ()
1215
+
1216
+ all_stats = []
1217
+
1218
+ for module , messages , stats , msg_status in pool .imap_unordered (
1219
+ _worker_check_single_file , files
1220
+ ):
1221
+ linter .set_current_module (module )
1222
+ for msg in messages :
1223
+ msg = Message (* msg )
1224
+ linter .reporter .handle_message (msg )
1225
+
1226
+ all_stats .append (stats )
1227
+ linter .msg_status |= msg_status
1228
+
1229
+ linter .stats = _merge_stats (all_stats )
1230
+
1231
+ # Insert stats data to local checkers.
1232
+ for checker in linter .get_checkers ():
1233
+ if checker is not linter :
1234
+ checker .stats = linter .stats
1235
+
1236
+
1237
+ # PyLinter object used by worker processes when checking files using multiprocessing
1238
+ # should only be used by the worker processes
1239
+ _worker_linter = None
1240
+
1241
+
1242
+ def _worker_initialize (linter ):
1243
+ global _worker_linter # pylint: disable=global-statement
1244
+ _worker_linter = linter
1245
+
1246
+ # On the worker process side the messages are just collected and passed back to
1247
+ # parent process as _worker_check_file function's return value
1248
+ _worker_linter .set_reporter (reporters .CollectingReporter ())
1249
+ _worker_linter .open ()
1250
+
1251
+
1252
+ def _worker_check_single_file (file_item ):
1253
+ name , filepath , modname = file_item
1254
+
1255
+ _worker_linter .open ()
1256
+ _worker_linter .check_single_file (name , filepath , modname )
1257
+
1258
+ msgs = [_get_new_args (m ) for m in _worker_linter .reporter .messages ]
1259
+ return (
1260
+ _worker_linter .current_name ,
1261
+ msgs ,
1262
+ _worker_linter .stats ,
1263
+ _worker_linter .msg_status ,
1264
+ )
1265
+
1266
+
1329
1267
# some reporting functions ####################################################
1330
1268
1331
1269
@@ -1479,6 +1417,10 @@ class Run:
1479
1417
),
1480
1418
)
1481
1419
1420
+ @staticmethod
1421
+ def _return_one (* args ): # pylint: disable=unused-argument
1422
+ return 1
1423
+
1482
1424
def __init__ (self , args , reporter = None , do_exit = True ):
1483
1425
self ._rcfile = None
1484
1426
self ._plugins = []
@@ -1504,7 +1446,7 @@ def __init__(self, args, reporter=None, do_exit=True):
1504
1446
"rcfile" ,
1505
1447
{
1506
1448
"action" : "callback" ,
1507
- "callback" : lambda * args : 1 ,
1449
+ "callback" : Run . _return_one ,
1508
1450
"type" : "string" ,
1509
1451
"metavar" : "<file>" ,
1510
1452
"help" : "Specify a configuration file." ,
@@ -1514,7 +1456,7 @@ def __init__(self, args, reporter=None, do_exit=True):
1514
1456
"init-hook" ,
1515
1457
{
1516
1458
"action" : "callback" ,
1517
- "callback" : lambda * args : 1 ,
1459
+ "callback" : Run . _return_one ,
1518
1460
"type" : "string" ,
1519
1461
"metavar" : "<code>" ,
1520
1462
"level" : 1 ,
0 commit comments