Przeglądaj źródła

[rust] [depot_tools] Minimal `rustfmt` support for `git cl format`.

This CL provides minimal `git cl format` support to enforce correct Rust
formatting in presubmit checks.  For now the files are always fully
formatted - there is no support at this point for formatting only the
changed lines.

Manual tests (after temporarily, artificially introducing a formatting
error to one of .rs files under build/rest/tests):

*) git cl presubmit
   Result: The src directory requires source formatting.
           Please run: git cl format

*) git cl format --dry-run
   Result: Pretty/colorful diff printed out.

*) git cl format
   Result: Temporary formatting errors are fixed.

Bug: chromium:1231317
Change-Id: I114ece90630476f27871ebcd170162caa92c0871
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/3054980
Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org>
Reviewed-by: Adrian Taylor <adetaylor@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: danakj <danakj@chromium.org>
Lukasz Anforowicz 3 lat temu
rodzic
commit
b4d3954db7
2 zmienionych plików z 122 dodań i 0 usunięć
  1. 49 0
      git_cl.py
  2. 73 0
      rustfmt.py

+ 49 - 0
git_cl.py

@@ -46,6 +46,7 @@ import owners_client
 import owners_finder
 import owners_finder
 import presubmit_canned_checks
 import presubmit_canned_checks
 import presubmit_support
 import presubmit_support
+import rustfmt
 import scm
 import scm
 import setup_color
 import setup_color
 import split_cl
 import split_cl
@@ -5065,6 +5066,34 @@ def _RunClangFormatDiff(opts, clang_diff_files, top_dir, upstream_commit):
   return return_value
   return return_value
 
 
 
 
+def _RunRustFmt(opts, rust_diff_files, top_dir, upstream_commit):
+  """Runs rustfmt.  Just like _RunClangFormatDiff returns 2 to indicate that
+  presubmit checks have failed (and returns 0 otherwise)."""
+
+  if not rust_diff_files:
+    return 0
+
+  # Locate the rustfmt binary.
+  try:
+    rustfmt_tool = rustfmt.FindRustfmtToolInChromiumTree()
+  except rustfmt.NotFoundError as e:
+    DieWithError(e)
+
+  # TODO(crbug.com/1231317): Support formatting only the changed lines
+  # if `opts.full or settings.GetFormatFullByDefault()` is False.  See also:
+  # https://github.com/emilio/rustfmt-format-diff
+  cmd = [rustfmt_tool]
+  if opts.dry_run:
+    cmd.append('--check')
+  cmd += rust_diff_files
+  rustfmt_exitcode = subprocess2.call(cmd)
+
+  if opts.presubmit and rustfmt_exitcode != 0:
+    return 2
+  else:
+    return 0
+
+
 def MatchingFileType(file_name, extensions):
 def MatchingFileType(file_name, extensions):
   """Returns True if the file name ends with one of the given extensions."""
   """Returns True if the file name ends with one of the given extensions."""
   return bool([ext for ext in extensions if file_name.lower().endswith(ext)])
   return bool([ext for ext in extensions if file_name.lower().endswith(ext)])
@@ -5076,6 +5105,7 @@ def CMDformat(parser, args):
   """Runs auto-formatting tools (clang-format etc.) on the diff."""
   """Runs auto-formatting tools (clang-format etc.) on the diff."""
   CLANG_EXTS = ['.cc', '.cpp', '.h', '.m', '.mm', '.proto', '.java']
   CLANG_EXTS = ['.cc', '.cpp', '.h', '.m', '.mm', '.proto', '.java']
   GN_EXTS = ['.gn', '.gni', '.typemap']
   GN_EXTS = ['.gn', '.gni', '.typemap']
+  RUST_EXTS = ['.rs']
   parser.add_option('--full', action='store_true',
   parser.add_option('--full', action='store_true',
                     help='Reformat the full content of all touched files')
                     help='Reformat the full content of all touched files')
   parser.add_option('--upstream', help='Branch to check against')
   parser.add_option('--upstream', help='Branch to check against')
@@ -5109,6 +5139,18 @@ def CMDformat(parser, args):
                     help='Print diff to stdout rather than modifying files.')
                     help='Print diff to stdout rather than modifying files.')
   parser.add_option('--presubmit', action='store_true',
   parser.add_option('--presubmit', action='store_true',
                     help='Used when running the script from a presubmit.')
                     help='Used when running the script from a presubmit.')
+
+  parser.add_option('--rust-fmt',
+                    dest='use_rust_fmt',
+                    action='store_true',
+                    default=rustfmt.IsRustfmtSupported(),
+                    help='Enables formatting of Rust file types using rustfmt.')
+  parser.add_option(
+      '--no-rust-fmt',
+      dest='use_rust_fmt',
+      action='store_false',
+      help='Disables formatting of Rust file types using rustfmt.')
+
   opts, args = parser.parse_args(args)
   opts, args = parser.parse_args(args)
 
 
   if opts.python is not None and opts.no_python:
   if opts.python is not None and opts.no_python:
@@ -5158,6 +5200,7 @@ def CMDformat(parser, args):
         x for x in diff_files if MatchingFileType(x, CLANG_EXTS)
         x for x in diff_files if MatchingFileType(x, CLANG_EXTS)
     ]
     ]
   python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
   python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
+  rust_diff_files = [x for x in diff_files if MatchingFileType(x, RUST_EXTS)]
   gn_diff_files = [x for x in diff_files if MatchingFileType(x, GN_EXTS)]
   gn_diff_files = [x for x in diff_files if MatchingFileType(x, GN_EXTS)]
 
 
   top_dir = settings.GetRoot()
   top_dir = settings.GetRoot()
@@ -5165,6 +5208,12 @@ def CMDformat(parser, args):
   return_value = _RunClangFormatDiff(opts, clang_diff_files, top_dir,
   return_value = _RunClangFormatDiff(opts, clang_diff_files, top_dir,
                                      upstream_commit)
                                      upstream_commit)
 
 
+  if opts.use_rust_fmt:
+    rust_fmt_return_value = _RunRustFmt(opts, rust_diff_files, top_dir,
+                                        upstream_commit)
+    if rust_fmt_return_value == 2:
+      return_value = 2
+
   # Similar code to above, but using yapf on .py files rather than clang-format
   # Similar code to above, but using yapf on .py files rather than clang-format
   # on C/C++ files
   # on C/C++ files
   py_explicitly_disabled = opts.python is not None and not opts.python
   py_explicitly_disabled = opts.python is not None and not opts.python

+ 73 - 0
rustfmt.py

@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Redirects to the version of rustfmt present in the Chrome tree.
+
+Rust binaries are pulled down from Google Cloud Storage whenever you sync
+Chrome. This script knows how to locate those tools, assuming the script is
+invoked from inside a Chromium checkout."""
+
+import gclient_paths
+import os
+import subprocess
+import sys
+
+
+class NotFoundError(Exception):
+  """A file could not be found."""
+
+  def __init__(self, e):
+    Exception.__init__(
+        self, 'Problem while looking for rustfmt in Chromium source tree:\n'
+        '%s' % e)
+
+
+def FindRustfmtToolInChromiumTree():
+  """Return a path to the rustfmt executable, or die trying."""
+  chromium_src_path = gclient_paths.GetPrimarySolutionPath()
+  if not chromium_src_path:
+    raise NotFoundError(
+        'Could not find checkout in any parent of the current path.\n'
+        'Set CHROMIUM_BUILDTOOLS_PATH to use outside of a chromium checkout.')
+
+  # TODO(lukasza): Deduplicate the is-Rust-supported and find-Rust-binaries code
+  # by somehow sharing the `rust_prefix` variable from //build/config/rust.gni
+  tool_path = os.path.join(chromium_src_path, 'third_party',
+                           'android_rust_toolchain', 'toolchain', 'bin',
+                           'rustfmt' + gclient_paths.GetExeSuffix())
+  if not os.path.exists(tool_path):
+    raise NotFoundError('File does not exist: %s' % tool_path)
+  return tool_path
+
+
+def IsRustfmtSupported():
+  try:
+    FindRustfmtToolInChromiumTree()
+    return True
+  except NotFoundError:
+    return False
+
+
+def main(args):
+  try:
+    tool = FindRustfmtToolInChromiumTree()
+  except NotFoundError as e:
+    sys.stderr.write("%s\n" % str(e))
+    return 1
+
+  # Add some visibility to --help showing where the tool lives, since this
+  # redirection can be a little opaque.
+  help_syntax = ('-h', '--help', '-help', '-help-list', '--help-list')
+  if any(match in args for match in help_syntax):
+    print('\nDepot tools redirects you to the rustfmt at:\n    %s\n' % tool)
+
+  return subprocess.call([tool] + args)
+
+
+if __name__ == '__main__':
+  try:
+    sys.exit(main(sys.argv[1:]))
+  except KeyboardInterrupt:
+    sys.stderr.write('interrupted\n')
+    sys.exit(1)