git-cl: Invoke presubmit checks via subprocess execution instead of via module.

PRESUBMIT.py scripts are executed in presubmit_support.py using exec().
Since PRESUBMIT.py scripts might not be yet compatible with python 3, we
have to execute presubmit_support.py using python 2.

git_cl.py imports presubmit_support.py, and executes presubmit checks using
presubmit_support as a module. This forces git_cl.py to be executed using
python 2 to maintain compatibility for PRESUBMIT.py scripts.

This change allows git_cl.py to be executed using python 3, while
presubmit_support.py is executed using python 2.

Similar changes for post-submit hooks and git-cl try masters will follow.

Bug: 1042324
Change-Id: Ic3bb1c2985459baf6aa04d0cc65017a1c2578153
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2068945
Reviewed-by: Anthony Polito <apolito@google.com>
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
diff --git a/git_cl.py b/git_cl.py
index 78174df..4d89021 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -68,6 +68,7 @@
 # depot_tools checkout.
 DEPOT_TOOLS = os.path.dirname(os.path.abspath(__file__))
 TRACES_DIR = os.path.join(DEPOT_TOOLS, 'traces')
+PRESUBMIT_SUPPORT = os.path.join(DEPOT_TOOLS, 'presubmit_support.py')
 
 # When collecting traces, Git hashes will be reduced to 6 characters to reduce
 # the size after compression.
@@ -1422,23 +1423,57 @@
 
     self.description = description
 
-  def RunHook(self, committing, may_prompt, verbose, change, parallel):
+  def RunHook(
+      self, committing, may_prompt, verbose, parallel, upstream, description,
+      all_files):
     """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
-    try:
-      start = time_time()
-      result = presubmit_support.DoPresubmitChecks(change, committing,
-          verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
-          default_presubmit=None, may_prompt=may_prompt,
-          gerrit_obj=self.GetGerritObjForPresubmit(),
-          parallel=parallel)
-      metrics.collector.add_repeated('sub_commands', {
-        'command': 'presubmit',
-        'execution_time': time_time() - start,
-        'exit_code': 0 if result.should_continue() else 1,
-      })
-      return result
-    except presubmit_support.PresubmitFailure as e:
-      DieWithError('%s\nMaybe your depot_tools is out of date?' % e)
+    args = [
+        '--commit' if committing else '--upload',
+        '--author', self.GetAuthor(),
+        '--root', settings.GetRoot(),
+        '--upstream', upstream,
+    ]
+
+    args.extend(['--verbose'] * verbose)
+
+    issue = self.GetIssue()
+    patchset = self.GetPatchset()
+    gerrit_url = self.GetCodereviewServer()
+    if issue:
+      args.extend(['--issue', str(issue)])
+    if patchset:
+      args.extend(['--patchset', str(patchset)])
+    if gerrit_url:
+      args.extend(['--gerrit_url', gerrit_url])
+
+    if may_prompt:
+      args.append('--may_prompt')
+    if parallel:
+      args.append('--parallel')
+    if all_files:
+      args.append('--all_files')
+
+    with gclient_utils.temporary_file() as description_file:
+      with gclient_utils.temporary_file() as json_output:
+        gclient_utils.FileWrite(
+            description_file, description.encode('utf-8'), mode='wb')
+        args.extend(['--json_output', json_output])
+        args.extend(['--description_file', description_file])
+
+        start = time_time()
+        p = subprocess2.Popen(['vpython', PRESUBMIT_SUPPORT] + args)
+        exit_code = p.wait()
+        metrics.collector.add_repeated('sub_commands', {
+          'command': 'presubmit',
+          'execution_time': time_time() - start,
+          'exit_code': exit_code,
+        })
+
+        if exit_code:
+          sys.exit(exit_code)
+
+        json_results = gclient_utils.FileRead(json_output)
+        return json.loads(json_results)
 
   def CMDUpload(self, options, git_diff_args, orig_args):
     """Uploads a change to codereview."""
@@ -1467,21 +1502,23 @@
       self.ExtendCC(watchlist.GetWatchersForPaths(files))
 
     if not options.bypass_hooks:
+      description = change.FullDescriptionText()
       if options.reviewers or options.tbrs or options.add_owners_to:
         # Set the reviewer list now so that presubmit checks can access it.
-        change_description = ChangeDescription(change.FullDescriptionText())
+        change_description = ChangeDescription(description)
         change_description.update_reviewers(options.reviewers,
                                             options.tbrs,
                                             options.add_owners_to,
                                             change)
-        change.SetDescriptionText(change_description.description)
+        description = change_description.description
       hook_results = self.RunHook(committing=False,
                                   may_prompt=not options.force,
                                   verbose=options.verbose,
-                                  change=change, parallel=options.parallel)
-      if not hook_results.should_continue():
-        return 1
-      self.ExtendCC(hook_results.more_cc)
+                                  parallel=options.parallel,
+                                  upstream=base_branch,
+                                  description=description,
+                                  all_files=False)
+      self.ExtendCC(hook_results['more_cc'])
 
     print_stats(git_diff_args)
     ret = self.CMDUploadChange(options, git_diff_args, custom_cl_base, change)
@@ -1982,14 +2019,19 @@
             action='submit')
       print('WARNING: Bypassing hooks and submitting latest uploaded patchset.')
     elif not bypass_hooks:
-      hook_results = self.RunHook(
+      upstream = self.GetCommonAncestorWithUpstream()
+      if self.GetIssue():
+        description = self.FetchDescription()
+      else:
+        description = self.GetDescription(upstream)
+      self.RunHook(
           committing=True,
           may_prompt=not force,
           verbose=verbose,
-          change=self.GetChange(self.GetCommonAncestorWithUpstream()),
-          parallel=parallel)
-      if not hook_results.should_continue():
-        return 1
+          parallel=parallel,
+          upstream=upstream,
+          description=description,
+          all_files=False)
 
     self.SubmitIssue(wait_for_merge=True)
     print('Issue %s has been submitted.' % self.GetIssueURL())
@@ -4085,27 +4127,19 @@
     # Default to diffing against the common ancestor of the upstream branch.
     base_branch = cl.GetCommonAncestorWithUpstream()
 
-  if options.all:
-    base_change = cl.GetChange(base_branch)
-    files = [('M', f) for f in base_change.AllFiles()]
-    change = presubmit_support.GitChange(
-        base_change.Name(),
-        base_change.FullDescriptionText(),
-        base_change.RepositoryRoot(),
-        files,
-        base_change.issue,
-        base_change.patchset,
-        base_change.author_email,
-        base_change._upstream)
+  if self.GetIssue():
+    description = self.FetchDescription()
   else:
-    change = cl.GetChange(base_branch)
+    description = self.GetDescription(base_branch)
 
   cl.RunHook(
       committing=not options.upload,
       may_prompt=False,
       verbose=options.verbose,
-      change=change,
-      parallel=options.parallel)
+      parallel=options.parallel,
+      upstream=base_branch,
+      description=description,
+      all_files=options.all)
   return 0