Commit b4d3954d authored by Lukasz Anforowicz's avatar Lukasz Anforowicz Committed by LUCI CQ

[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: 's avatarAdrian Taylor <adetaylor@chromium.org>
Reviewed-by: 's avatarDirk Pranke <dpranke@google.com>
Reviewed-by: 's avatardanakj <danakj@chromium.org>
parent 76a0b2c6
......@@ -46,6 +46,7 @@ import owners_client
import owners_finder
import presubmit_canned_checks
import presubmit_support
import rustfmt
import scm
import setup_color
import split_cl
......@@ -5065,6 +5066,34 @@ def _RunClangFormatDiff(opts, clang_diff_files, top_dir, upstream_commit):
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):
"""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)])
......@@ -5076,6 +5105,7 @@ def CMDformat(parser, args):
"""Runs auto-formatting tools (clang-format etc.) on the diff."""
CLANG_EXTS = ['.cc', '.cpp', '.h', '.m', '.mm', '.proto', '.java']
GN_EXTS = ['.gn', '.gni', '.typemap']
RUST_EXTS = ['.rs']
parser.add_option('--full', action='store_true',
help='Reformat the full content of all touched files')
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.')
parser.add_option('--presubmit', action='store_true',
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)
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)
]
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)]
top_dir = settings.GetRoot()
......@@ -5165,6 +5208,12 @@ def CMDformat(parser, args):
return_value = _RunClangFormatDiff(opts, clang_diff_files, top_dir,
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
# on C/C++ files
py_explicitly_disabled = opts.python is not None and not opts.python
......
#!/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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment