Skip to content

Commit

Permalink
Add clang tidy tooling (pytorch#7412)
Browse files Browse the repository at this point in the history
  • Loading branch information
goldsborough authored and soumith committed May 9, 2018
1 parent 769397e commit 23be4ac
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .clang-tidy
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:
...
196 changes: 196 additions & 0 deletions tools/clang_tidy.py
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()
1 change: 1 addition & 0 deletions tools/cpp_build/libtorch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ cmake_policy(VERSION 3.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if (VERBOSE)
message(STATUS "ATEN_PATH is ${ATEN_PATH}")
Expand Down

0 comments on commit 23be4ac

Please sign in to comment.