build_tools/clang_tidy.py (99 lines of code) (raw):
#!/usr/bin/env python3
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Most of the code are inspired by https://github.com/apache/kudu/blob/856fa3404b00ee02bd3bc1d77d414ede2b2cd02e/build-support/clang_tidy_gerrit.py
import argparse
import collections
import json
import multiprocessing
from multiprocessing.pool import ThreadPool
import os
import re
import subprocess
import sys
import tempfile
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
BUILD_PATH = os.path.join(ROOT, "build", "latest")
def run_tidy(sha="HEAD", is_rev_range=False):
diff_cmdline = ["git", "diff" if is_rev_range else "show", sha]
# Figure out which paths changed in the given diff.
changed_paths = subprocess.check_output(diff_cmdline + ["--name-only", "--pretty=format:"]).splitlines()
changed_paths = [p for p in changed_paths if p]
# Produce a separate diff for each file and run clang-tidy-diff on it
# in parallel.
#
# Note: this will incorporate any configuration from .clang-tidy.
def tidy_on_path(path):
patch_file = tempfile.NamedTemporaryFile()
cmd = diff_cmdline + [
"--src-prefix=%s/" % ROOT,
"--dst-prefix=%s/" % ROOT,
"--",
path]
subprocess.check_call(cmd, stdout=patch_file, cwd=ROOT)
# TODO(yingchun): some checks could be disabled before we fix them.
# "-checks=-llvm-include-order,-modernize-concat-nested-namespaces,-cppcoreguidelines-macro-usage,-cppcoreguidelines-special-member-functions,-hicpp-special-member-functions,-bugprone-easily-swappable-parameters,-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-modernize-avoid-c-arrays,-llvm-header-guard,-cppcoreguidelines-pro-bounds-pointer-arithmetic",
cmdline = ["clang-tidy-diff",
"-clang-tidy-binary",
"clang-tidy",
"-p0",
"-path", BUILD_PATH,
# Disable some checks that are not useful for us now.
# They are sorted by names, and should be consistent to .clang-tidy.
"-checks=-bugprone-easily-swappable-parameters,"
"-bugprone-lambda-function-name,"
"-bugprone-macro-parentheses,"
"-bugprone-sizeof-expression,"
"-cert-err58-cpp,"
"-clang-analyzer-cplusplus.NewDelete,"
"-concurrency-mt-unsafe,"
"-cppcoreguidelines-avoid-c-arrays,"
"-cppcoreguidelines-avoid-magic-numbers,"
"-cppcoreguidelines-avoid-non-const-global-variables,"
"-cppcoreguidelines-macro-usage,"
"-cppcoreguidelines-non-private-member-variables-in-classes,"
"-cppcoreguidelines-owning-memory,"
"-cppcoreguidelines-pro-bounds-array-to-pointer-decay,"
"-cppcoreguidelines-pro-bounds-pointer-arithmetic,"
"-cppcoreguidelines-pro-type-const-cast,"
"-cppcoreguidelines-pro-type-reinterpret-cast,"
"-cppcoreguidelines-pro-type-union-access,"
"-fuchsia-default-arguments-calls,"
"-fuchsia-multiple-inheritance,"
"-fuchsia-overloaded-operator,"
"-fuchsia-statically-constructed-objects,"
"-google-readability-avoid-underscore-in-googletest-name,"
"-hicpp-avoid-c-arrays,"
"-hicpp-named-parameter,"
"-hicpp-no-array-decay,"
"-llvm-header-guard,"
"-llvm-include-order,"
"-misc-definitions-in-headers,"
"-misc-non-private-member-variables-in-classes,"
"-misc-unused-parameters,"
"-modernize-avoid-bind,"
"-modernize-avoid-c-arrays,"
"-modernize-replace-disallow-copy-and-assign-macro,"
"-modernize-use-trailing-return-type,"
"-performance-unnecessary-value-param,"
"-readability-function-cognitive-complexity,"
"-readability-identifier-length,"
"-readability-magic-numbers,"
"-readability-named-parameter,"
"-readability-suspicious-call-argument",
"-extra-arg=-language=c++",
"-extra-arg=-std=c++17",
"-extra-arg=-Ithirdparty/output/include"]
return subprocess.check_output(
cmdline,
stdin=open(patch_file.name),
cwd=ROOT).decode()
pool = ThreadPool(multiprocessing.cpu_count())
try:
return "".join(pool.imap(tidy_on_path, changed_paths))
except KeyboardInterrupt as ki:
sys.exit(1)
finally:
pool.terminate()
pool.join()
if __name__ == "__main__":
# Basic setup and argument parsing.
parser = argparse.ArgumentParser(description="Run clang-tidy on a patch")
parser.add_argument("--rev-range", action="store_true",
default=False,
help="Whether the revision specifies the 'rev..' range")
parser.add_argument('rev', help="The git revision (or range of revisions) to process")
args = parser.parse_args()
# Run clang-tidy and parse the output.
clang_output = run_tidy(args.rev, args.rev_range)
parsed = re.match(r'.+(warning|error): .+', clang_output, re.MULTILINE | re.DOTALL)
print(clang_output, file=sys.stderr)
if not parsed:
print("No warnings", file=sys.stderr)
sys.exit(0)
sys.exit(1)