Add support for running PRESUBMIT.py checks under Python2 or 3.

This CL adds support for running PRESUBMIT.py under either Python2
or Python3 as specified in each PRESUBMIT.py file.

To run the checks under Python3, the PRESUBMIT.py file must contain
a line exactly matching "^USE_PYTHON3 = True$". If the file
does not contain this string, the checks will run under Python2.

Different PRESUBMIT.py files in a single CL may thus contain
a mix of 2- and 3-compatible checks, but each individual file will
only be run in one or the other (it doesn't likely make sense to run
them in both by default).

Bug: 1157663
Change-Id: Ic74977941a6519388089328b6e1dfba2e885924b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2832654
Commit-Queue: Dirk Pranke <dpranke@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
diff --git a/git_cl.py b/git_cl.py
index 5482382..87904a9 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -1321,6 +1321,23 @@
     if all_files:
       args.append('--all_files')
 
+    if resultdb and not realm:
+      # TODO (crbug.com/1113463): store realm somewhere and look it up so
+      # it is not required to pass the realm flag
+      print('Note: ResultDB reporting will NOT be performed because --realm'
+            ' was not specified. To enable ResultDB, please run the command'
+            ' again with the --realm argument to specify the LUCI realm.')
+
+    py2_results = self._RunPresubmit(args, resultdb, realm, description,
+                                     use_python3=False)
+    py3_results = self._RunPresubmit(args, resultdb, realm, description,
+                                     use_python3=True)
+    return self._MergePresubmitResults(py2_results, py3_results)
+
+  def _RunPresubmit(self, args, resultdb, realm, description, use_python3):
+    args = args[:]
+    vpython = 'vpython3' if use_python3 else 'vpython'
+
     with gclient_utils.temporary_file() as description_file:
       with gclient_utils.temporary_file() as json_output:
         gclient_utils.FileWrite(description_file, description)
@@ -1328,15 +1345,9 @@
         args.extend(['--description_file', description_file])
 
         start = time_time()
-        cmd = ['vpython', PRESUBMIT_SUPPORT] + args
+        cmd = [vpython, PRESUBMIT_SUPPORT] + args
         if resultdb and realm:
           cmd = ['rdb', 'stream', '-new', '-realm', realm, '--'] + cmd
-        elif resultdb:
-          # TODO (crbug.com/1113463): store realm somewhere and look it up so
-          # it is not required to pass the realm flag
-          print('Note: ResultDB reporting will NOT be performed because --realm'
-                ' was not specified. To enable ResultDB, please run the command'
-                ' again with the --realm argument to specify the LUCI realm.')
 
         p = subprocess2.Popen(cmd)
         exit_code = p.wait()
@@ -1353,6 +1364,19 @@
         json_results = gclient_utils.FileRead(json_output)
         return json.loads(json_results)
 
+  def _MergePresubmitResults(self, py2_results, py3_results):
+    return {
+        'more_cc': sorted(set(py2_results.get('more_cc', []) +
+                              py3_results.get('more_cc', []))),
+        'errors': (
+            py2_results.get('errors', []) + py3_results.get('errors', [])),
+        'notifications': (
+            py2_results.get('notifications', []) +
+            py3_results.get('notifications', [])),
+        'warnings': (
+            py2_results.get('warnings', []) + py3_results.get('warnings', []))
+    }
+
   def RunPostUploadHook(self, verbose, upstream, description):
     args = self._GetCommonPresubmitArgs(verbose, upstream)
     args.append('--post_upload')