Parallelize pylint PRESUBMIT checks.

R=maruel@chromium.org
BUG=479837,499650

Review URL: https://codereview.chromium.org/1181103002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@295664 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/presubmit_canned_checks.py b/presubmit_canned_checks.py
index 9179fb6..edd6332 100644
--- a/presubmit_canned_checks.py
+++ b/presubmit_canned_checks.py
@@ -771,20 +771,28 @@
   env['PYTHONPATH'] = input_api.os_path.pathsep.join(
       extra_paths_list + sys.path).encode('utf8')
 
-  def GetPylintCmd(files):
+  def GetPylintCmd(files, extra, parallel):
     # Windows needs help running python files so we explicitly specify
     # the interpreter to use. It also has limitations on the size of
     # the command-line, so we pass arguments via a pipe.
+    cmd = [input_api.python_executable,
+           input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
+           '--args-on-stdin']
     if len(files) == 1:
       description = files[0]
     else:
       description = '%s files' % len(files)
 
+    if extra:
+      cmd.extend(extra)
+      description += ' using %s' % (extra,)
+    if parallel:
+      cmd.append('--jobs=%s' % input_api.cpu_count)
+      description += ' on %d cores' % input_api.cpu_count
+
     return input_api.Command(
         name='Pylint (%s)' % description,
-        cmd=[input_api.python_executable,
-             input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
-             '--args-on-stdin'],
+        cmd=cmd,
         kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
         message=error_type)
 
@@ -797,9 +805,14 @@
   # a quick local edit to diagnose pylint issues more
   # easily.
   if True:
-    return [GetPylintCmd(files)]
+    # pylint's cycle detection doesn't work in parallel, so spawn a second,
+    # single-threaded job for just that check.
+    return [
+      GetPylintCmd(files, ["--disable=cyclic-import"], True),
+      GetPylintCmd(files, ["--disable=all", "--enable=cyclic-import"], False)
+    ]
   else:
-    return map(lambda x: GetPylintCmd([x]), files)
+    return map(lambda x: GetPylintCmd([x], extra_args, 1), files)
 
 
 def RunPylint(input_api, *args, **kwargs):
diff --git a/presubmit_support.py b/presubmit_support.py
index 0abd421..4baee77 100755
--- a/presubmit_support.py
+++ b/presubmit_support.py
@@ -311,6 +311,8 @@
     # InputApi.platform is the platform you're currently running on.
     self.platform = sys.platform
 
+    self.cpu_count = multiprocessing.cpu_count()
+
     # The local path of the currently-being-processed presubmit script.
     self._current_presubmit_path = os.path.dirname(presubmit_path)
 
diff --git a/tests/presubmit_unittest.py b/tests/presubmit_unittest.py
index 0d2b4d6..f37bf6c 100755
--- a/tests/presubmit_unittest.py
+++ b/tests/presubmit_unittest.py
@@ -1183,17 +1183,15 @@
     self.mox.ReplayAll()
     members = [
       'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedSourceFiles',
-      'AffectedTextFiles',
-      'DEFAULT_BLACK_LIST', 'DEFAULT_WHITE_LIST',
-      'DepotToLocalPath', 'FilterSourceFile', 'LocalPaths',
-      'LocalToDepotPath', 'Command', 'RunTests',
-      'PresubmitLocalPath', 'ReadFile', 'RightHandSideLines', 'ServerPaths',
-      'basename', 'cPickle', 'cpplint', 'cStringIO', 'canned_checks', 'change',
-      'environ', 'glob', 'host_url', 'is_committing', 'json', 'logging',
-      'marshal', 'os_listdir', 'os_walk', 'os_path', 'os_stat', 'owners_db',
-      'pickle', 'platform', 'python_executable', 're', 'rietveld', 'subprocess',
-      'tbr', 'tempfile', 'time', 'traceback', 'unittest', 'urllib2', 'version',
-      'verbose',
+      'AffectedTextFiles', 'DEFAULT_BLACK_LIST', 'DEFAULT_WHITE_LIST',
+      'DepotToLocalPath', 'FilterSourceFile', 'LocalPaths', 'LocalToDepotPath',
+      'Command', 'RunTests', 'PresubmitLocalPath', 'ReadFile',
+      'RightHandSideLines', 'ServerPaths', 'basename', 'cPickle', 'cpplint',
+      'cStringIO', 'canned_checks', 'change', 'cpu_count', 'environ', 'glob',
+      'host_url', 'is_committing', 'json', 'logging', 'marshal', 'os_listdir',
+      'os_walk', 'os_path', 'os_stat', 'owners_db', 'pickle', 'platform',
+      'python_executable', 're', 'rietveld', 'subprocess', 'tbr', 'tempfile',
+      'time', 'traceback', 'unittest', 'urllib2', 'version', 'verbose',
     ]
     # If this test fails, you should add the relevant test.
     self.compareMembers(
@@ -1852,6 +1850,7 @@
     input_api.tbr = False
     input_api.python_executable = 'pyyyyython'
     input_api.platform = sys.platform
+    input_api.cpu_count = 2
     input_api.time = time
     input_api.canned_checks = presubmit_canned_checks
     input_api.Command = presubmit.CommandData
@@ -2525,7 +2524,12 @@
     pylintrc = os.path.join(_ROOT, 'pylintrc')
 
     CommHelper(input_api,
-        ['pyyyyython', pylint, '--args-on-stdin'],
+        ['pyyyyython', pylint, '--args-on-stdin', '--disable=cyclic-import',
+         '--jobs=2'],
+        env=mox.IgnoreArg(), stdin='file1.py\n--rcfile=%s' % pylintrc)
+    CommHelper(input_api,
+        ['pyyyyython', pylint, '--args-on-stdin', '--disable=all',
+         '--enable=cyclic-import'],
         env=mox.IgnoreArg(), stdin='file1.py\n--rcfile=%s' % pylintrc)
     self.mox.ReplayAll()