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

@ -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)

@ -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

@ -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,224 +48,274 @@ 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."""
result = './' result = './'
while not os.path.isfile(os.path.join(result, path)): while not os.path.isfile(os.path.join(result, path)):
if os.path.realpath(result) == '/': if os.path.realpath(result) == '/':
print('Error: could not find compilation database.') print('Error: could not find compilation database.')
sys.exit(1) sys.exit(1)
result += '../' result += '../'
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
"""Gets a command line for clang-tidy.""" return os.path.normpath(os.path.join(directory, f))
start = [clang_tidy_binary]
if header_filter is not None:
start.append('-header-filter=' + header_filter) def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
else: header_filter, extra_arg, extra_arg_before, quiet,
# Show warnings in all in-project headers by default. config):
start.append('-header-filter=^' + build_path + '/.*') """Gets a command line for clang-tidy."""
if checks: start = [clang_tidy_binary]
start.append('-checks=' + checks) if header_filter is not None:
if warningsaserrors: start.append('-header-filter=' + header_filter)
start.append('-warnings-as-errors=' + warningsaserrors) if checks:
if tmpdir is not None: start.append('-checks=' + checks)
start.append('-export-fixes') if tmpdir is not None:
# Get a temporary file. We immediately close the handle so clang-tidy can start.append('-export-fixes')
# overwrite it. # Get a temporary file. We immediately close the handle so clang-tidy can
(handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) # overwrite it.
os.close(handle) (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
start.append(name) os.close(handle)
for arg in extra_arg: start.append(name)
start.append('-extra-arg=%s' % arg) for arg in extra_arg:
for arg in extra_arg_before: start.append('-extra-arg=%s' % arg)
start.append('-extra-arg-before=%s' % arg) for arg in extra_arg_before:
start.append('-p=' + build_path) start.append('-extra-arg-before=%s' % arg)
if quiet: start.append('-p=' + build_path)
start.append('-quiet') if quiet:
start.append(f) start.append('-quiet')
return start if config:
start.append('-config=' + config)
start.append(f)
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:
subprocess.check_call([args.clang_apply_replacements_binary, '--version']) subprocess.check_call([args.clang_apply_replacements_binary, '--version'])
except: except:
print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' print('Unable to run clang-apply-replacements. Is clang-apply-replacements '
'binary correctly specified?', file=sys.stderr) 'binary correctly specified?', file=sys.stderr)
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
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')
if args.style: if args.style:
invocation.append('-style=' + args.style) invocation.append('-style=' + args.style)
invocation.append(tmpdir) invocation.append(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()
def main(): def main():
parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' parser = argparse.ArgumentParser(description='Runs clang-tidy over all files '
'in a compilation database. Requires ' 'in a compilation database. Requires '
'clang-tidy and clang-apply-replacements in ' 'clang-tidy and clang-apply-replacements in '
'$PATH.') '$PATH.')
parser.add_argument('-clang-tidy-binary', metavar='PATH', parser.add_argument('-clang-tidy-binary', metavar='PATH',
default='clang-tidy', default='clang-tidy',
help='path to clang-tidy binary') help='path to clang-tidy binary')
parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', parser.add_argument('-clang-apply-replacements-binary', metavar='PATH',
default='clang-apply-replacements', default='clang-apply-replacements',
help='path to clang-apply-replacements binary') help='path to clang-apply-replacements binary')
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: \'*\', '
parser.add_argument('-header-filter', default=None, ' CheckOptions: [{key: x, '
help='regular expression matching the names of the ' ' value: y}]}" '
'headers to output diagnostics from. Diagnostics from ' 'When the value is empty, clang-tidy will '
'the main file of each translation unit are always ' 'attempt to find a file named .clang-tidy for '
'displayed.') 'each source file in its parent directories.')
parser.add_argument('-j', type=int, default=0, parser.add_argument('-header-filter', default=None,
help='number of tidy instances to be run in parallel.') help='regular expression matching the names of the '
parser.add_argument('files', nargs='*', default=['.*'], 'headers to output diagnostics from. Diagnostics from '
help='files to be processed (regex on path)') 'the main file of each translation unit are always '
parser.add_argument('-fix', action='store_true', help='apply fix-its') 'displayed.')
parser.add_argument('-format', action='store_true', help='Reformat code ' if yaml:
'after applying fixes') parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes',
parser.add_argument('-style', default='file', help='The style of reformat ' help='Create a yaml file to store suggested fixes in, '
'code after applying fixes') 'which can be applied with clang-apply-replacements.')
parser.add_argument('-p', dest='build_path', parser.add_argument('-j', type=int, default=0,
help='Path used to read a compile command database.') help='number of tidy instances to be run in parallel.')
parser.add_argument('-extra-arg', dest='extra_arg', parser.add_argument('files', nargs='*', default=['.*'],
action='append', default=[], help='files to be processed (regex on path)')
help='Additional argument to append to the compiler ' parser.add_argument('-fix', action='store_true', help='apply fix-its')
'command line.') parser.add_argument('-format', action='store_true', help='Reformat code '
parser.add_argument('-extra-arg-before', dest='extra_arg_before', 'after applying fixes')
action='append', default=[], parser.add_argument('-style', default='file', help='The style of reformat '
help='Additional argument to prepend to the compiler ' 'code after applying fixes')
'command line.') parser.add_argument('-p', dest='build_path',
parser.add_argument('-quiet', action='store_true', help='Path used to read a compile command database.')
help='Run clang-tidy in quiet mode') parser.add_argument('-extra-arg', dest='extra_arg',
parser.add_argument('-no-command-on-stdout', action='store_true', action='append', default=[],
help='Run clang-tidy without printing invocation on ' help='Additional argument to append to the compiler '
'stdout') 'command line.')
args = parser.parse_args() parser.add_argument('-extra-arg-before', dest='extra_arg_before',
action='append', default=[],
help='Additional argument to prepend to the compiler '
'command line.')
parser.add_argument('-quiet', action='store_true',
help='Run clang-tidy in quiet mode')
args = parser.parse_args()
db_path = 'compile_commands.json' db_path = 'compile_commands.json'
if args.build_path is not None: if args.build_path is not None:
build_path = args.build_path build_path = args.build_path
else:
# Find our database
build_path = find_compilation_database(db_path)
try:
invocation = [args.clang_tidy_binary, '-list-checks']
invocation.append('-p=' + build_path)
if args.checks:
invocation.append('-checks=' + args.checks)
invocation.append('-')
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: else:
# Find our database subprocess.check_call(invocation)
build_path = find_compilation_database(db_path) except:
print("Unable to run clang-tidy.", file=sys.stderr)
sys.exit(1)
# Load the database and extract all files.
database = json.load(open(os.path.join(build_path, db_path)))
files = [make_absolute(entry['file'], entry['directory'])
for entry in database]
max_task = args.j
if max_task == 0:
max_task = multiprocessing.cpu_count()
tmpdir = None
if args.fix or (yaml and args.export_fixes):
check_clang_apply_replacements_binary(args)
tmpdir = tempfile.mkdtemp()
# Build up a big regexy filter from all command line arguments.
file_name_re = re.compile('|'.join(args.files))
return_code = 0
try:
# Spin up a bunch of tidy-launching threads.
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):
t = threading.Thread(target=run_tidy,
args=(args, tmpdir, build_path, task_queue, lock, failed_files))
t.daemon = True
t.start()
# Fill the queue with files.
for name in files:
if file_name_re.search(name):
task_queue.put(name)
# Wait for all threads to be done.
task_queue.join()
if len(failed_files):
return_code = 1
except KeyboardInterrupt:
# This is a sad hack. Unfortunately subprocess goes
# bonkers with ctrl-c and we start forking merrily.
print('\nCtrl-C detected, goodbye.')
if tmpdir:
shutil.rmtree(tmpdir)
os.kill(0, 9)
if yaml and args.export_fixes:
print('Writing fixes to ' + args.export_fixes + ' ...')
try: try:
invocation = [args.clang_tidy_binary, '-list-checks', '-p=' + build_path] merge_replacement_files(tmpdir, args.export_fixes)
if args.checks:
invocation.append('-checks=' + args.checks)
if args.warningsaserrors:
invocation.append('-warnings-as-errors=' + args.warningsaserrors)
invocation.append('-')
print(subprocess.check_output(invocation))
except: except:
print("Unable to run clang-tidy.", file=sys.stderr) print('Error exporting fixes.\n', file=sys.stderr)
sys.exit(1) traceback.print_exc()
return_code=1
# Load the database and extract all files.
database = json.load(open(os.path.join(build_path, db_path)))
files = [entry['file'] for entry in database]
max_task = args.j
if max_task == 0:
max_task = multiprocessing.cpu_count()
tmpdir = None
if args.fix:
check_clang_apply_replacements_binary(args)
tmpdir = tempfile.mkdtemp()
# Build up a big regexy filter from all command line arguments.
file_name_re = re.compile('|'.join(args.files))
if args.fix:
print('Applying fixes ...')
try: try:
# Spin up a bunch of tidy-launching threads. apply_fixes(args, tmpdir)
queue = TidyQueue(max_task) except:
for _ in range(max_task): print('Error applying fixes.\n', file=sys.stderr)
t = threading.Thread(target=run_tidy, traceback.print_exc()
args=(args, tmpdir, build_path, queue)) return_code=1
t.daemon = True
t.start()
# Fill the queue with files.
for name in files:
if file_name_re.search(name):
queue.put(name)
# Wait for all threads to be done.
queue.join()
# If one clang-tidy process found and error, exit with non-zero
# status
if queue.has_error:
sys.exit(2)
except KeyboardInterrupt:
# This is a sad hack. Unfortunately subprocess goes
# bonkers with ctrl-c and we start forking merrily.
print('\nCtrl-C detected, goodbye.')
if args.fix:
shutil.rmtree(tmpdir)
os.kill(0, 9)
if args.fix:
print('Applying fixes ...')
successfully_applied = False
try:
apply_fixes(args, tmpdir)
successfully_applied = True
except:
print('Error applying fixes.\n', file=sys.stderr)
traceback.print_exc()
shutil.rmtree(tmpdir)
if not successfully_applied:
sys.exit(1)
if tmpdir:
shutil.rmtree(tmpdir)
sys.exit(return_code)
if __name__ == '__main__': if __name__ == '__main__':
main() main()