Skip to content

Commit 08c3ed7

Browse files
authored
Merge pull request #34 from StephanKa/feature/add-scripts-clang-tidy-clang-format-cmake-targets
added scripts for clang-tidy and clang-format, added different cmake targets
2 parents 8cf8a67 + 39bf59e commit 08c3ed7

4 files changed

Lines changed: 994 additions & 6 deletions

File tree

cmake/CodeFormat.cmake

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1+
FIND_PACKAGE(Python COMPONENTS Interpreter REQUIRED)
2+
13
# To exclude directories from the format check, add corresponding clang-format config files into those directories.
4+
ADD_CUSTOM_TARGET(clang-format-check
5+
USES_TERMINAL
6+
COMMAND ${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/run-clang-format.py -warnings-as-errors
7+
)
8+
9+
ADD_CUSTOM_TARGET(clang-format-check-fix
10+
USES_TERMINAL
11+
COMMAND ${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/run-clang-format.py -fix
12+
)
213

3-
ADD_CUSTOM_TARGET(format-check
14+
ADD_CUSTOM_TARGET(clang-tidy-check
415
USES_TERMINAL
5-
COMMAND ${CMAKE_SOURCE_DIR}/scripts/run-clang-format.py -warnings-as-errors
6-
)
16+
COMMAND ${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/run-clang-tidy.py
17+
)
718

8-
ADD_CUSTOM_TARGET(format-check-fix
19+
ADD_CUSTOM_TARGET(clang-tidy-diff-check
920
USES_TERMINAL
10-
COMMAND ${CMAKE_SOURCE_DIR}/scripts/run-clang-format.py -fix
11-
)
21+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
22+
COMMAND git diff -U0 HEAD --no-prefix | ${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/run-clang-tidy-diff.py -path ${CMAKE_BINARY_DIR}
23+
)

scripts/run-clang-format.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
This script will call clang-format.
3+
4+
Call example: run-clang-format.py - Parallel clang-format runner
5+
6+
Based on run-clang-tidy.py, which is part of the LLVM Project, under the
7+
Apache License v2.0 with LLVM Exceptions.
8+
See https://llvm.org/LICENSE.txt for license information.
9+
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
10+
"""
11+
12+
import argparse
13+
import json
14+
import multiprocessing
15+
import os
16+
import subprocess
17+
import sys
18+
import threading
19+
import queue as queue
20+
21+
22+
def find_compilation_database(path):
23+
"""Adjust the directory until a compilation database is found."""
24+
result = './'
25+
while not os.path.isfile(os.path.join(result, path)):
26+
if os.path.realpath(result) == '/':
27+
print('Error: could not find compilation database.')
28+
sys.exit(1)
29+
result += '../'
30+
return os.path.realpath(result)
31+
32+
33+
def make_absolute(f, directory):
34+
"""Create a absolute path from given parameters."""
35+
if os.path.isabs(f):
36+
return f
37+
return os.path.normpath(os.path.join(directory, f))
38+
39+
40+
def get_format_invocation(f, clang_format_binary, fix, warnings_as_errors, quiet):
41+
"""Get a command line for clang-format."""
42+
start = [clang_format_binary]
43+
if fix:
44+
start.append('-i')
45+
else:
46+
start.append('--dry-run')
47+
if warnings_as_errors:
48+
start.append('--Werror')
49+
start.append(f)
50+
return start
51+
52+
53+
def run_format(args, _, queue, lock, failed_files):
54+
"""Take filenames out of queue and runs clang-format on them."""
55+
while True:
56+
name = queue.get()
57+
invocation = get_format_invocation(name, args.clang_format_binary, args.fix, args.warnings_as_errors,
58+
args.quiet)
59+
60+
proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
61+
output, err = proc.communicate()
62+
if proc.returncode != 0:
63+
failed_files.append(name)
64+
with lock:
65+
sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8'))
66+
if len(err) > 0:
67+
sys.stdout.flush()
68+
sys.stderr.write(err.decode('utf-8'))
69+
queue.task_done()
70+
71+
72+
if __name__ == '__main__':
73+
parser = argparse.ArgumentParser(description='Runs clang-format over all files '
74+
'in a compilation database. Requires '
75+
'clang-format in $PATH.')
76+
parser.add_argument('-clang-format-binary', metavar='PATH', default='clang-format',
77+
help='path to clang-format binary')
78+
parser.add_argument('-p', dest='build_path', help='Path used to read a compile command database.')
79+
parser.add_argument('-j', type=int, default=0, help='number of tidy instances to be run in parallel.')
80+
parser.add_argument('-fix', action='store_true', help='reformat files')
81+
parser.add_argument('-warnings-as-errors', action='store_true',
82+
help='Let the clang-tidy process return != 0 if a check failed.')
83+
parser.add_argument('-quiet', action='store_true', help='Run clang-format in quiet mode')
84+
args = parser.parse_args()
85+
86+
db_path = 'compile_commands.json'
87+
88+
if args.build_path is not None:
89+
build_path = args.build_path
90+
else:
91+
# Find our database
92+
build_path = find_compilation_database(db_path)
93+
94+
try:
95+
with open(os.devnull, 'w') as dev_null:
96+
subprocess.check_call([args.clang_format_binary, '--dump-config'], stdout=dev_null)
97+
except Exception as ex:
98+
print(f'Unable to run clang-format. {ex} - {sys.stderr}')
99+
sys.exit(1)
100+
101+
# Load the database and extract all files.
102+
database = json.load(open(os.path.join(build_path, db_path)))
103+
files = [make_absolute(entry['file'], entry['directory']) for entry in database]
104+
105+
max_task = args.j
106+
if max_task == 0:
107+
max_task = multiprocessing.cpu_count()
108+
109+
return_code = 0
110+
try:
111+
# Spin up a bunch of format-launching threads.
112+
task_queue = queue.Queue(max_task)
113+
# List of files with a non-zero return code.
114+
failed_files = []
115+
lock = threading.Lock()
116+
for _ in range(max_task):
117+
t = threading.Thread(target=run_format, args=(args, build_path, task_queue, lock, failed_files))
118+
t.daemon = True
119+
t.start()
120+
121+
# Fill the queue with files.
122+
for name in files:
123+
task_queue.put(name)
124+
125+
# Wait for all threads to be done.
126+
task_queue.join()
127+
if len(failed_files):
128+
return_code = 1
129+
130+
except KeyboardInterrupt:
131+
# This is a sad hack. Unfortunately subprocess goes
132+
# bonkers with ctrl-c and we start forking merrily.
133+
print('\nCtrl-C detected, goodbye.')
134+
os.kill(0, 9)
135+
136+
sys.exit(return_code)

0 commit comments

Comments
 (0)