From b9b3e3cb36aa3943b838dd579c9211be64e80249 Mon Sep 17 00:00:00 2001 From: Boian Petkantchin Date: Wed, 18 Jan 2023 10:38:03 -0800 Subject: [PATCH] Add github job for code formatting check --- .github/workflows/lint.yml | 32 +++++ third_party/format_diff/README.md | 1 + third_party/format_diff/format_diff.py | 165 +++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 .github/workflows/lint.yml create mode 100644 third_party/format_diff/README.md create mode 100755 third_party/format_diff/format_diff.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5d9ceb2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,32 @@ +# Copyright 2022 The IREE Authors +# +# Licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Lint + +on: [pull_request] + +jobs: + yapf: + runs-on: ubuntu-20.04 + steps: + - name: Checking out repository + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - name: Setting up python + uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0 + - name: Fetching Base Branch + # We have to explicitly fetch the base branch as well + run: git fetch --no-tags --prune --depth=1 origin "${GITHUB_BASE_REF?}:${GITHUB_BASE_REF?}" + - name: Install yapf + run: | + python3 -m pip install yapf==0.32.0 + - name: Run format_diff.py with yapf + run: | + git diff -U0 "${GITHUB_BASE_REF?}" | python3 third_party/format_diff/format_diff.py yapf -i + git diff --exit-code + - name: Instructions for fixing the above linting errors + if: ${{ failure() }} + run: | + printf "You can fix the lint errors above by running\n" diff --git a/third_party/format_diff/README.md b/third_party/format_diff/README.md new file mode 100644 index 0000000..7f47e13 --- /dev/null +++ b/third_party/format_diff/README.md @@ -0,0 +1 @@ +Forked from https://github.com/llvm/llvm-project/blob/master/clang/tools/clang-format/clang-format-diff.py diff --git a/third_party/format_diff/format_diff.py b/third_party/format_diff/format_diff.py new file mode 100755 index 0000000..0fda38d --- /dev/null +++ b/third_party/format_diff/format_diff.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# +#===- format_diff.py - Diff Reformatter ----*- python3 -*--===# +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===------------------------------------------------------------------------===# +""" +This script reads input from a unified diff and reformats all the changed +lines. This is useful to reformat all the lines touched by a specific patch. +Example usage: + + git diff -U0 HEAD^ | python3 format_diff.py yapf -i + git diff -U0 HEAD^ | python3 format_diff.py clang-format -i + svn diff --diff-cmd=diff -x-U0 | python3 format_diff.py -p0 clang-format -i + +General usage: + | python3 format_diff.py [--regex] [--lines-style] [-p] binary [args for binary] + +It should be noted that the filename contained in the diff is used unmodified +to determine the source file to update. Users calling this script directly +should be careful to ensure that the path in the diff is correct relative to the +current working directory. +""" + +import argparse +import difflib +import io +import re +import subprocess +import sys + +BINARY_TO_DEFAULT_REGEX = { + "yapf": r".*\.py", + "clang-format": + r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|hxx|m|mm|inc|js|ts|proto|" + r"protodevel|java|cs)", +} + + +def parse_arguments(): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + "binary", + help="Location of binary to use for formatting. This controls the " + "default values of --regex and --lines-style. If binary isn't 'yapf' " + "or 'clang-format' then --regex and --lines-style are required.") + parser.add_argument( + "--regex", + metavar="PATTERN", + default=None, + help="Regex pattern for selecting file paths to reformat from the piped " + "diff. This flag is required if 'binary' is not set to 'yapf' or " + "'clang-format'. Otherwise, this flag overrides the default pattern that " + "--binary sets.") + parser.add_argument( + "--lines-style", + default=None, + help="How to style the 'lines' argument for the given binary. Can be set " + "to 'yapf' or 'clang-format'. This flag is required if 'binary' is not " + "set to 'yapf' or 'clang-format'.") + parser.add_argument( + "-p", + metavar="NUM", + default=1, + help="Strip the smallest prefix containing P slashes. Set to 0 if " + "passing `--no-prefix` to `git diff` or using `svn`") + + # Parse and error-check arguments + args, binary_args = parser.parse_known_args() + if args.binary not in BINARY_TO_DEFAULT_REGEX: + if not args.regex: + raise parser.error("If 'binary' is not 'yapf' or 'clang-format' then " + "--regex must be set.") + if not args.lines_style: + raise parser.error("If 'binary' is not 'yapf' or 'clang-format' then " + "--lines-style must be set.") + else: + # Set defaults based off of 'binary'. + if not args.regex: + args.regex = BINARY_TO_DEFAULT_REGEX[args.binary] + if not args.lines_style: + args.lines_style = args.binary + + if args.lines_style not in ["yapf", "clang-format"]: + raise parser.error(f"Unexpected value for --line-style {args.lines_style}") + + return args, binary_args + + +def main(): + args, binary_args = parse_arguments() + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + # Match all filenames. + match = re.search(fr"^\+\+\+\ (.*?/){{{args.p}}}(\S*)", line) + if match: + filename = match.group(2) + if filename is None: + continue + + # Match all filenames specified by --regex. + if not re.match(f"^{args.regex}$", filename): + continue + + # Match unified diff line numbers. + match = re.search(r"^@@.*\+(\d+)(,(\d+))?", line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + + if args.lines_style == "yapf": + lines = ["--lines", f"{start_line}-{end_line}"] + elif args.lines_style == "clang-format": + lines = ["-lines", f"{start_line}:{end_line}"] + lines_by_file.setdefault(filename, []).extend(lines) + + # Pass the changed lines to 'binary' alongside any unparsed args (e.g. -i). + for filename, lines in lines_by_file.items(): + command = [args.binary, filename] + command.extend(lines) + command.extend(binary_args) + + print(f"Running `{' '.join(command)}`") + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=None, + stdin=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) + + # If the formatter printed the formatted code to stdout then print out + # a unified diff between the formatted and unformatted code. + # If flags like --verbose are passed to the binary then the diffs this + # produces won't be particularly helpful. + formatted_code = io.StringIO(stdout).readlines() + if len(formatted_code): + with open(filename) as f: + unformatted_code = f.readlines() + diff = difflib.unified_diff(unformatted_code, + formatted_code, + fromfile=filename, + tofile=filename, + fromfiledate="(before formatting)", + tofiledate="(after formatting)") + diff_string = "".join(diff) + if len(diff_string) > 0: + sys.stdout.write(diff_string) + + +if __name__ == "__main__": + main()