Update clang-tidy configuration and scripts

This commit is contained in:
sfan5 2020-03-11 13:33:54 +01:00
parent 7908b20dd9
commit 8546d6089a
3 changed files with 260 additions and 211 deletions

View File

@ -1,4 +1,5 @@
Checks: '-*,modernize-use-emplace,modernize-use-default-member-init,modernize-use-equals-delete,modernize-use-equals-default,modernize-return-braced-init-list,modernize-loop-convert,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-string-compare,misc-inefficient-algorithm,misc-inaccurate-erase,misc-incorrect-roundings,misc-unconventional-assign-operator,bugprone-suspicious-memset-usage,performance-*' Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*'
WarningsAsErrors: '-*,modernize-use-emplace,performance-type-promotion-in-math-fn,performance-faster-string-find,performance-implicit-cast-in-loop'
CheckOptions: CheckOptions:
- key: modernize-use-default-member-init.UseAssignment - key: performance-unnecessary-value-param.AllowedTypes
value: True value: v[23]f;v[23][su](16|32)

View File

@ -7,8 +7,6 @@ if [ -z "${CLANG_TIDY}" ]; then
CLANG_TIDY=clang-tidy CLANG_TIDY=clang-tidy
fi fi
files_to_analyze="$(find src/ -name '*.cpp' -or -name '*.h')"
mkdir -p cmakebuild && cd cmakebuild mkdir -p cmakebuild && cd cmakebuild
cmake -DCMAKE_BUILD_TYPE=Debug \ cmake -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
@ -20,11 +18,11 @@ make GenerateVersion
cd .. cd ..
echo "Performing clang-tidy checks..." echo "Performing clang-tidy checks..."
./util/travis/run-clang-tidy.py -clang-tidy-binary=${CLANG_TIDY} -p cmakebuild \ ./util/travis/run-clang-tidy.py \
-checks='-*,modernize-use-emplace,modernize-avoid-bind,performance-*' \ -clang-tidy-binary=${CLANG_TIDY} -p cmakebuild \
-warningsaserrors='-*,modernize-use-emplace,performance-type-promotion-in-math-fn,performance-faster-string-find,performance-implicit-cast-in-loop' \ -quiet -config="$(cat .clang-tidy)" \
-no-command-on-stdout -quiet \ 'src/.*'
files 'src/.*'
RET=$? RET=$?
echo "Clang tidy returned $RET" echo "Clang tidy returned $RET"
exit $RET exit $RET

View File

@ -1,13 +1,12 @@
#!/usr/bin/env python2 #!/usr/bin/env python
# #
# ===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# #===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===#
# #
# The LLVM Compiler Infrastructure # Part of the LLVM Project, 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 file is distributed under the University of Illinois Open Source #===------------------------------------------------------------------------===#
# License. See LICENSE.TXT for details.
#
# ===------------------------------------------------------------------------===#
# FIXME: Integrate with clang-tidy-diff.py # FIXME: Integrate with clang-tidy-diff.py
""" """
@ -35,11 +34,12 @@ http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
""" """
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import glob
import json import json
import multiprocessing import multiprocessing
import os import os
import Queue
import re import re
import shutil import shutil
import subprocess import subprocess
@ -48,12 +48,17 @@ import tempfile
import threading import threading
import traceback import traceback
try:
import yaml
except ImportError:
yaml = None
class TidyQueue(Queue.Queue): is_py2 = sys.version[0] == '2'
def __init__(self, max_task):
Queue.Queue.__init__(self, max_task)
self.has_error = False
if is_py2:
import Queue as queue
else:
import queue as queue
def find_compilation_database(path): def find_compilation_database(path):
"""Adjusts the directory until a compilation database is found.""" """Adjusts the directory until a compilation database is found."""
@ -66,20 +71,21 @@ def find_compilation_database(path):
return os.path.realpath(result) return os.path.realpath(result)
def get_tidy_invocation(f, clang_tidy_binary, checks, warningsaserrors, def make_absolute(f, directory):
tmpdir, build_path, if os.path.isabs(f):
header_filter, extra_arg, extra_arg_before, quiet): return f
return os.path.normpath(os.path.join(directory, f))
def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
header_filter, extra_arg, extra_arg_before, quiet,
config):
"""Gets a command line for clang-tidy.""" """Gets a command line for clang-tidy."""
start = [clang_tidy_binary] start = [clang_tidy_binary]
if header_filter is not None: if header_filter is not None:
start.append('-header-filter=' + header_filter) start.append('-header-filter=' + header_filter)
else:
# Show warnings in all in-project headers by default.
start.append('-header-filter=^' + build_path + '/.*')
if checks: if checks:
start.append('-checks=' + checks) start.append('-checks=' + checks)
if warningsaserrors:
start.append('-warnings-as-errors=' + warningsaserrors)
if tmpdir is not None: if tmpdir is not None:
start.append('-export-fixes') start.append('-export-fixes')
# Get a temporary file. We immediately close the handle so clang-tidy can # Get a temporary file. We immediately close the handle so clang-tidy can
@ -94,10 +100,37 @@ def get_tidy_invocation(f, clang_tidy_binary, checks, warningsaserrors,
start.append('-p=' + build_path) start.append('-p=' + build_path)
if quiet: if quiet:
start.append('-quiet') start.append('-quiet')
if config:
start.append('-config=' + config)
start.append(f) start.append(f)
return start return start
def merge_replacement_files(tmpdir, mergefile):
"""Merge all replacement files in a directory into a single file"""
# The fixes suggested by clang-tidy >= 4.0.0 are given under
# the top level key 'Diagnostics' in the output yaml files
mergekey="Diagnostics"
merged=[]
for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')):
content = yaml.safe_load(open(replacefile, 'r'))
if not content:
continue # Skip empty files.
merged.extend(content.get(mergekey, []))
if merged:
# MainSourceFile: The key is required by the definition inside
# include/clang/Tooling/ReplacementsYaml.h, but the value
# is actually never used inside clang-apply-replacements,
# so we set it to '' here.
output = { 'MainSourceFile': '', mergekey: merged }
with open(mergefile, 'w') as out:
yaml.safe_dump(output, out)
else:
# Empty the file:
open(mergefile, 'w').close()
def check_clang_apply_replacements_binary(args): def check_clang_apply_replacements_binary(args):
"""Checks if invoking supplied clang-apply-replacements binary works.""" """Checks if invoking supplied clang-apply-replacements binary works."""
try: try:
@ -110,7 +143,7 @@ def check_clang_apply_replacements_binary(args):
def apply_fixes(args, tmpdir): def apply_fixes(args, tmpdir):
"""Calls clang-apply-fixes on a given directory. Deletes the dir when done.""" """Calls clang-apply-fixes on a given directory."""
invocation = [args.clang_apply_replacements_binary] invocation = [args.clang_apply_replacements_binary]
if args.format: if args.format:
invocation.append('-format') invocation.append('-format')
@ -120,20 +153,19 @@ def apply_fixes(args, tmpdir):
subprocess.call(invocation) subprocess.call(invocation)
def run_tidy(args, tmpdir, build_path, queue): def run_tidy(args, tmpdir, build_path, queue, lock, failed_files):
"""Takes filenames out of queue and runs clang-tidy on them.""" """Takes filenames out of queue and runs clang-tidy on them."""
while True: while True:
name = queue.get() name = queue.get()
invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks,
args.warningsaserrors, tmpdir, build_path, tmpdir, build_path, args.header_filter,
args.header_filter, args.extra_arg, args.extra_arg, args.extra_arg_before,
args.extra_arg_before, args.quiet) args.quiet, args.config)
if not args.no_command_on_stdout:
sys.stdout.write(' '.join(invocation) + '\n') proc = subprocess.Popen(invocation)
try: proc.wait()
subprocess.check_call(invocation) if proc.returncode != 0:
except subprocess.CalledProcessError: failed_files.append(name)
queue.has_error = True
queue.task_done() queue.task_done()
@ -151,14 +183,23 @@ def main():
parser.add_argument('-checks', default=None, parser.add_argument('-checks', default=None,
help='checks filter, when not specified, use clang-tidy ' help='checks filter, when not specified, use clang-tidy '
'default') 'default')
parser.add_argument('-warningsaserrors', default=None, parser.add_argument('-config', default=None,
help='warnings-as-errors filter, when not specified, ' help='Specifies a configuration in YAML/JSON format: '
'use clang-tidy default') ' -config="{Checks: \'*\', '
' CheckOptions: [{key: x, '
' value: y}]}" '
'When the value is empty, clang-tidy will '
'attempt to find a file named .clang-tidy for '
'each source file in its parent directories.')
parser.add_argument('-header-filter', default=None, parser.add_argument('-header-filter', default=None,
help='regular expression matching the names of the ' help='regular expression matching the names of the '
'headers to output diagnostics from. Diagnostics from ' 'headers to output diagnostics from. Diagnostics from '
'the main file of each translation unit are always ' 'the main file of each translation unit are always '
'displayed.') 'displayed.')
if yaml:
parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes',
help='Create a yaml file to store suggested fixes in, '
'which can be applied with clang-apply-replacements.')
parser.add_argument('-j', type=int, default=0, parser.add_argument('-j', type=int, default=0,
help='number of tidy instances to be run in parallel.') help='number of tidy instances to be run in parallel.')
parser.add_argument('files', nargs='*', default=['.*'], parser.add_argument('files', nargs='*', default=['.*'],
@ -180,9 +221,6 @@ def main():
'command line.') 'command line.')
parser.add_argument('-quiet', action='store_true', parser.add_argument('-quiet', action='store_true',
help='Run clang-tidy in quiet mode') help='Run clang-tidy in quiet mode')
parser.add_argument('-no-command-on-stdout', action='store_true',
help='Run clang-tidy without printing invocation on '
'stdout')
args = parser.parse_args() args = parser.parse_args()
db_path = 'compile_commands.json' db_path = 'compile_commands.json'
@ -194,78 +232,90 @@ def main():
build_path = find_compilation_database(db_path) build_path = find_compilation_database(db_path)
try: try:
invocation = [args.clang_tidy_binary, '-list-checks', '-p=' + build_path] invocation = [args.clang_tidy_binary, '-list-checks']
invocation.append('-p=' + build_path)
if args.checks: if args.checks:
invocation.append('-checks=' + args.checks) invocation.append('-checks=' + args.checks)
if args.warningsaserrors:
invocation.append('-warnings-as-errors=' + args.warningsaserrors)
invocation.append('-') invocation.append('-')
print(subprocess.check_output(invocation)) if args.quiet:
# Even with -quiet we still want to check if we can call clang-tidy.
with open(os.devnull, 'w') as dev_null:
subprocess.check_call(invocation, stdout=dev_null)
else:
subprocess.check_call(invocation)
except: except:
print("Unable to run clang-tidy.", file=sys.stderr) print("Unable to run clang-tidy.", file=sys.stderr)
sys.exit(1) sys.exit(1)
# Load the database and extract all files. # Load the database and extract all files.
database = json.load(open(os.path.join(build_path, db_path))) database = json.load(open(os.path.join(build_path, db_path)))
files = [entry['file'] for entry in database] files = [make_absolute(entry['file'], entry['directory'])
for entry in database]
max_task = args.j max_task = args.j
if max_task == 0: if max_task == 0:
max_task = multiprocessing.cpu_count() max_task = multiprocessing.cpu_count()
tmpdir = None tmpdir = None
if args.fix: if args.fix or (yaml and args.export_fixes):
check_clang_apply_replacements_binary(args) check_clang_apply_replacements_binary(args)
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
# Build up a big regexy filter from all command line arguments. # Build up a big regexy filter from all command line arguments.
file_name_re = re.compile('|'.join(args.files)) file_name_re = re.compile('|'.join(args.files))
return_code = 0
try: try:
# Spin up a bunch of tidy-launching threads. # Spin up a bunch of tidy-launching threads.
queue = TidyQueue(max_task) task_queue = queue.Queue(max_task)
# List of files with a non-zero return code.
failed_files = []
lock = threading.Lock()
for _ in range(max_task): for _ in range(max_task):
t = threading.Thread(target=run_tidy, t = threading.Thread(target=run_tidy,
args=(args, tmpdir, build_path, queue)) args=(args, tmpdir, build_path, task_queue, lock, failed_files))
t.daemon = True t.daemon = True
t.start() t.start()
# Fill the queue with files. # Fill the queue with files.
for name in files: for name in files:
if file_name_re.search(name): if file_name_re.search(name):
queue.put(name) task_queue.put(name)
# Wait for all threads to be done. # Wait for all threads to be done.
queue.join() task_queue.join()
if len(failed_files):
# If one clang-tidy process found and error, exit with non-zero return_code = 1
# status
if queue.has_error:
sys.exit(2)
except KeyboardInterrupt: except KeyboardInterrupt:
# This is a sad hack. Unfortunately subprocess goes # This is a sad hack. Unfortunately subprocess goes
# bonkers with ctrl-c and we start forking merrily. # bonkers with ctrl-c and we start forking merrily.
print('\nCtrl-C detected, goodbye.') print('\nCtrl-C detected, goodbye.')
if args.fix: if tmpdir:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
os.kill(0, 9) os.kill(0, 9)
if yaml and args.export_fixes:
print('Writing fixes to ' + args.export_fixes + ' ...')
try:
merge_replacement_files(tmpdir, args.export_fixes)
except:
print('Error exporting fixes.\n', file=sys.stderr)
traceback.print_exc()
return_code=1
if args.fix: if args.fix:
print('Applying fixes ...') print('Applying fixes ...')
successfully_applied = False
try: try:
apply_fixes(args, tmpdir) apply_fixes(args, tmpdir)
successfully_applied = True
except: except:
print('Error applying fixes.\n', file=sys.stderr) print('Error applying fixes.\n', file=sys.stderr)
traceback.print_exc() traceback.print_exc()
return_code=1
if tmpdir:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
if not successfully_applied: sys.exit(return_code)
sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
main() main()