forked from pytorch/pytorch
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add clang tidy tooling (pytorch#7412)
- Loading branch information
1 parent
769397e
commit 23be4ac
Showing
3 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
# NOTE: there must be no spaces before the '-', so put the comma first. | ||
Checks: ' | ||
* | ||
,modernize-* | ||
,clang-analyzer-* | ||
,-clang-diagnostic-* | ||
,-hicpp-no-array-decay | ||
,-fuchsia-* | ||
,-google-readability-namespace-comments | ||
,-llvm-namespace-comment | ||
,-google-readability-todo | ||
,-cppcoreguidelines-pro-bounds-array-to-pointer-decay | ||
,-cert-err60-cpp | ||
,-llvm-header-guard | ||
,-cppcoreguidelines-special-member-functions | ||
,-misc-unused-parameters | ||
,-hicpp-braces-around-statements | ||
,-hicpp-special-member-functions | ||
,-readability-braces-around-statements | ||
,-modernize-use-default-member-init | ||
,-google-runtime-references | ||
,-cppcoreguidelines-pro-type-vararg | ||
,-google-readability-braces-around-statements | ||
,-google-build-using-namespace | ||
,-hicpp-vararg | ||
,-hicpp-explicit-conversions | ||
,-performance-unnecessary-value-param | ||
,-google-runtime-references | ||
,-cppcoreguidelines-pro-type-static-cast-downcast | ||
,-cppcoreguidelines-pro-bounds-constant-array-index | ||
,-cert-err58-cpp | ||
,-modernize-make-unique | ||
,-cppcoreguidelines-owning-memory | ||
' | ||
WarningsAsErrors: '' | ||
HeaderFilterRegex: 'torch/csrc/' | ||
AnalyzeTemporaryDtors: false | ||
CheckOptions: | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
#!/usr/bin/env python | ||
|
||
import argparse | ||
import json | ||
import os.path | ||
import re | ||
import subprocess | ||
import sys | ||
|
||
DEFAULT_FILE_PATTERN = r".*\.[ch](pp)?" | ||
|
||
# @@ -start,count +start,count @@ | ||
CHUNK_PATTERN = r"^@@\s+-\d+,\d+\s+\+(\d+)(?:,(\d+))?\s+@@" | ||
|
||
|
||
def run_shell_command(arguments, process_name=None): | ||
"""Executes a shell command.""" | ||
assert len(arguments) > 0 | ||
try: | ||
output = subprocess.check_output(arguments, stderr=subprocess.STDOUT) | ||
except OSError: | ||
_, e, _ = sys.exc_info() | ||
process_name = process_name or arguments[0] | ||
raise RuntimeError("Error executing {}: {}".format(process_name, e)) | ||
else: | ||
return output.decode() | ||
|
||
|
||
def transform_globs_into_regexes(globs): | ||
"""Turns glob patterns into regular expressions.""" | ||
return [glob.replace("*", ".*").replace("?", ".") for glob in globs] | ||
|
||
|
||
def get_file_patterns(globs, regexes): | ||
"""Returns a list of compiled regex objects from globs and regex pattern strings.""" | ||
regexes += transform_globs_into_regexes(globs) | ||
if not regexes: | ||
regexes = [DEFAULT_FILE_PATTERN] | ||
return [re.compile(regex + "$") for regex in regexes] | ||
|
||
|
||
def git_diff(args, verbose): | ||
"""Executes a git diff command in the shell and returns its output.""" | ||
# --no-pager gets us the plain output, without pagination. | ||
# --no-color removes color codes. | ||
command = ["git", "--no-pager", "diff", "--no-color"] + args | ||
if verbose: | ||
print(" ".join(command)) | ||
return run_shell_command(command, process_name="git diff") | ||
|
||
|
||
def filter_files(files, file_patterns): | ||
"""Returns all files that match any of the patterns.""" | ||
filtered = [] | ||
for file in files: | ||
for pattern in file_patterns: | ||
if pattern.match(file): | ||
filtered.append(file) | ||
return filtered | ||
|
||
|
||
def get_changed_files(revision, paths, verbose): | ||
"""Runs git diff to get the paths of all changed files.""" | ||
# --diff-filter AMU gets us files that are (A)dded, (M)odified or (U)nmerged (in the working copy). | ||
# --name-only makes git diff return only the file paths, without any of the source changes. | ||
args = ["--diff-filter", "AMU", "--ignore-all-space", "--name-only", revision] | ||
output = git_diff(args + paths, verbose) | ||
return output.split("\n") | ||
|
||
|
||
def get_all_files(paths): | ||
"""Yields all files in any of the given paths""" | ||
for path in paths: | ||
for root, _, files in os.walk(path): | ||
for file in files: | ||
yield os.path.join(root, file) | ||
|
||
|
||
def get_changed_lines(revision, filename, verbose): | ||
"""Runs git diff to get the line ranges of all file changes.""" | ||
output = git_diff(["--unified=0", revision, filename], verbose) | ||
changed_lines = [] | ||
for chunk in re.finditer(CHUNK_PATTERN, output, re.MULTILINE): | ||
start = int(chunk.group(1)) | ||
count = int(chunk.group(2) or 1) | ||
changed_lines.append([start, start + count]) | ||
|
||
return {"name": filename, "lines": changed_lines} | ||
|
||
|
||
def run_clang_tidy(options, line_filters, files): | ||
"""Executes the actual clang-tidy command in the shell.""" | ||
command = [options.clang_tidy_exe, "-p", options.compile_commands_dir] | ||
if not options.config_file and os.path.exists(".clang-tidy"): | ||
options.config_file = ".clang-tidy" | ||
if options.config_file: | ||
import yaml | ||
|
||
with open(options.config_file) as config: | ||
# Here we convert the YAML config file to a JSON blob. | ||
command += ["-config", json.dumps(yaml.load(config))] | ||
if options.checks: | ||
command += ["-checks", options.checks] | ||
if line_filters: | ||
command += ["-line-filter", json.dumps(line_filters)] | ||
command += ["-{}".format(arg) for arg in options.extra_args] | ||
command += files | ||
|
||
if options.verbose: | ||
print(" ".join(command)) | ||
if options.show_command_only: | ||
command = [re.sub(r"^([{[].*[]}])$", r"'\1'", arg) for arg in command] | ||
return " ".join(command) | ||
|
||
return run_shell_command(command) | ||
|
||
|
||
def parse_options(): | ||
parser = argparse.ArgumentParser(description="Run Clang-Tidy (on your Git changes)") | ||
parser.add_argument( | ||
"-c", | ||
"--clang-tidy-exe", | ||
default="clang-tidy", | ||
help="{ath to clang-tidy executable", | ||
) | ||
parser.add_argument( | ||
"-e", | ||
"--extra-args", | ||
nargs="+", | ||
default=[], | ||
help="Extra arguments to forward to clang-tidy", | ||
) | ||
parser.add_argument( | ||
"-g", | ||
"--glob", | ||
nargs="+", | ||
default=[], | ||
help="File patterns as UNIX globs (no **)", | ||
) | ||
parser.add_argument( | ||
"-x", "--regex", nargs="+", default=[], help="File patterns as regexes" | ||
) | ||
parser.add_argument( | ||
"-d", | ||
"--compile-commands-dir", | ||
default=".", | ||
help="Path to the folder containing compile_commands.json", | ||
) | ||
parser.add_argument("-r", "--revision", help="Git revision to get changes from") | ||
parser.add_argument( | ||
"-p", "--paths", nargs="+", default=["."], help="Lint only the given paths" | ||
) | ||
parser.add_argument( | ||
"-s", | ||
"--show-command-only", | ||
action="store_true", | ||
help="Only show the command to be executed, without running it", | ||
) | ||
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") | ||
parser.add_argument( | ||
"--config-file", | ||
help="Path to a clang-tidy config file. Defaults to '.clang-tidy'.", | ||
) | ||
parser.add_argument( | ||
"--checks", help="Appends checks to those from the config file (if any)" | ||
) | ||
return parser.parse_args() | ||
|
||
|
||
def main(): | ||
options = parse_options() | ||
if options.revision: | ||
files = get_changed_files(options.revision, options.paths, options.verbose) | ||
else: | ||
files = get_all_files(options.paths) | ||
file_patterns = get_file_patterns(options.glob, options.regex) | ||
files = filter_files(files, file_patterns) | ||
|
||
# clang-tidy error's when it does not get input files. | ||
if not files: | ||
print("No files detected.") | ||
sys.exit() | ||
|
||
line_filters = [] | ||
if options.revision: | ||
for filename in files: | ||
changed_lines = get_changed_lines( | ||
options.revision, filename, options.verbose | ||
) | ||
line_filters.append(changed_lines) | ||
|
||
print(run_clang_tidy(options, line_filters, files)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters