Hendrik Langer
5 years ago
7 changed files with 410 additions and 0 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,94 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
|
||||
|
import codecs |
||||
|
import json |
||||
|
import os.path |
||||
|
import re |
||||
|
import subprocess |
||||
|
import sys |
||||
|
|
||||
|
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) |
||||
|
basepath = os.path.join(root_path, 'esphome') |
||||
|
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') |
||||
|
|
||||
|
|
||||
|
def shlex_quote(s): |
||||
|
if not s: |
||||
|
return u"''" |
||||
|
if re.search(r'[^\w@%+=:,./-]', s) is None: |
||||
|
return s |
||||
|
|
||||
|
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" |
||||
|
|
||||
|
def build_all_include(): |
||||
|
# Build a cpp file that includes all header files in this repo. |
||||
|
# Otherwise header-only integrations would not be tested by clang-tidy |
||||
|
headers = [] |
||||
|
for path in walk_files(basepath): |
||||
|
filetypes = ('.h',) |
||||
|
ext = os.path.splitext(path)[1] |
||||
|
if ext in filetypes: |
||||
|
path = os.path.relpath(path, root_path) |
||||
|
include_p = path.replace(os.path.sep, '/') |
||||
|
headers.append('#include "{}"'.format(include_p)) |
||||
|
headers.sort() |
||||
|
headers.append('') |
||||
|
content = '\n'.join(headers) |
||||
|
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f: |
||||
|
f.write(content) |
||||
|
|
||||
|
def build_compile_commands(): |
||||
|
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json') |
||||
|
if not os.path.isfile(gcc_flags_json): |
||||
|
print("Could not find {} file which is required for clang-tidy.") |
||||
|
print('Please run "pio init --ide atom" in the root esphome folder to generate that file.') |
||||
|
sys.exit(1) |
||||
|
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f: |
||||
|
gcc_flags = json.load(f) |
||||
|
exec_path = gcc_flags['execPath'] |
||||
|
include_paths = gcc_flags['gccIncludePaths'].split(',') |
||||
|
includes = ['-I{}'.format(p) for p in include_paths] |
||||
|
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') |
||||
|
defines = [flag for flag in cpp_flags if flag.startswith('-D')] |
||||
|
command = [exec_path] |
||||
|
command.extend(includes) |
||||
|
command.extend(defines) |
||||
|
command.append('-std=gnu++11') |
||||
|
command.append('-Wall') |
||||
|
command.append('-Wno-delete-non-virtual-dtor') |
||||
|
command.append('-Wno-unused-variable') |
||||
|
command.append('-Wunreachable-code') |
||||
|
|
||||
|
source_files = [] |
||||
|
for path in walk_files(basepath): |
||||
|
filetypes = ('.cpp',) |
||||
|
ext = os.path.splitext(path)[1] |
||||
|
if ext in filetypes: |
||||
|
source_files.append(os.path.abspath(path)) |
||||
|
source_files.append(temp_header_file) |
||||
|
source_files.sort() |
||||
|
compile_commands = [{ |
||||
|
'directory': root_path, |
||||
|
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])), |
||||
|
'file': p |
||||
|
} for p in source_files] |
||||
|
compile_commands_json = os.path.join(root_path, 'compile_commands.json') |
||||
|
if os.path.isfile(compile_commands_json): |
||||
|
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f: |
||||
|
try: |
||||
|
if json.load(f) == compile_commands: |
||||
|
return |
||||
|
except: |
||||
|
pass |
||||
|
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f: |
||||
|
json.dump(compile_commands, f, indent=2) |
||||
|
|
||||
|
|
||||
|
def walk_files(path): |
||||
|
for root, _, files in os.walk(path): |
||||
|
for name in files: |
||||
|
yield os.path.join(root, name) |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
build_compile_commands() |
||||
|
build_all_include() |
@ -0,0 +1,170 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
|
||||
|
from __future__ import print_function |
||||
|
|
||||
|
import multiprocessing |
||||
|
import os |
||||
|
import re |
||||
|
|
||||
|
import pexpect |
||||
|
import shutil |
||||
|
import subprocess |
||||
|
import sys |
||||
|
import tempfile |
||||
|
|
||||
|
import argparse |
||||
|
import click |
||||
|
import threading |
||||
|
|
||||
|
sys.path.append(os.path.dirname(__file__)) |
||||
|
from helpers import basepath, shlex_quote, get_output, build_compile_commands, \ |
||||
|
build_all_include, temp_header_file, git_ls_files, filter_changed |
||||
|
|
||||
|
is_py2 = sys.version[0] == '2' |
||||
|
|
||||
|
if is_py2: |
||||
|
import Queue as queue |
||||
|
else: |
||||
|
import queue as queue |
||||
|
|
||||
|
|
||||
|
def run_tidy(args, tmpdir, queue, lock, failed_files): |
||||
|
print("tidy") |
||||
|
while True: |
||||
|
path = queue.get() |
||||
|
invocation = ['clang-tidy-7', '-header-filter=^{}/.*'.format(re.escape(basepath))] |
||||
|
if tmpdir is not None: |
||||
|
invocation.append('-export-fixes') |
||||
|
# Get a temporary file. We immediately close the handle so clang-tidy can |
||||
|
# overwrite it. |
||||
|
(handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) |
||||
|
os.close(handle) |
||||
|
invocation.append(name) |
||||
|
invocation.append('-p=.') |
||||
|
if args.quiet: |
||||
|
invocation.append('-quiet') |
||||
|
for arg in ['-Wfor-loop-analysis', '-Wshadow-field', '-Wshadow-field-in-constructor']: |
||||
|
invocation.append('-extra-arg={}'.format(arg)) |
||||
|
invocation.append(os.path.abspath(path)) |
||||
|
invocation_s = ' '.join(shlex_quote(x) for x in invocation) |
||||
|
|
||||
|
# Use pexpect for a pseudy-TTY with colored output |
||||
|
output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', |
||||
|
timeout=15*60) |
||||
|
with lock: |
||||
|
if rc != 0: |
||||
|
print() |
||||
|
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) |
||||
|
print(invocation_s) |
||||
|
print(output) |
||||
|
print() |
||||
|
failed_files.append(path) |
||||
|
queue.task_done() |
||||
|
|
||||
|
|
||||
|
def progress_bar_show(value): |
||||
|
if value is None: |
||||
|
return '' |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
parser = argparse.ArgumentParser() |
||||
|
parser.add_argument('-j', '--jobs', type=int, |
||||
|
default=multiprocessing.cpu_count(), |
||||
|
help='number of tidy instances to be run in parallel.') |
||||
|
parser.add_argument('files', nargs='*', default=[], |
||||
|
help='files to be processed (regex on path)') |
||||
|
parser.add_argument('--fix', action='store_true', help='apply fix-its') |
||||
|
parser.add_argument('-q', '--quiet', action='store_false', |
||||
|
help='Run clang-tidy in quiet mode') |
||||
|
parser.add_argument('-c', '--changed', action='store_true', |
||||
|
help='Only run on changed files') |
||||
|
parser.add_argument('--all-headers', action='store_true', |
||||
|
help='Create a dummy file that checks all headers') |
||||
|
args = parser.parse_args() |
||||
|
|
||||
|
try: |
||||
|
get_output('clang-tidy-7', '-version') |
||||
|
except: |
||||
|
print(""" |
||||
|
Oops. It looks like clang-tidy is not installed. |
||||
|
|
||||
|
Please check you can run "clang-tidy-7 -version" in your terminal and install |
||||
|
clang-tidy (v7) if necessary. |
||||
|
|
||||
|
Note you can also upload your code as a pull request on GitHub and see the CI check |
||||
|
output to apply clang-tidy. |
||||
|
""") |
||||
|
return 1 |
||||
|
|
||||
|
build_all_include() |
||||
|
build_compile_commands() |
||||
|
|
||||
|
print("test") |
||||
|
|
||||
|
files = [] |
||||
|
for path in git_ls_files(): |
||||
|
filetypes = ('.cpp',) |
||||
|
ext = os.path.splitext(path)[1] |
||||
|
if ext in filetypes: |
||||
|
path = os.path.relpath(path, os.getcwd()) |
||||
|
files.append(path) |
||||
|
# Match against re |
||||
|
file_name_re = re.compile('|'.join(args.files)) |
||||
|
files = [p for p in files if file_name_re.search(p)] |
||||
|
|
||||
|
if args.changed: |
||||
|
files = filter_changed(files) |
||||
|
|
||||
|
files.sort() |
||||
|
|
||||
|
if args.all_headers: |
||||
|
files.insert(0, temp_header_file) |
||||
|
|
||||
|
tmpdir = None |
||||
|
if args.fix: |
||||
|
tmpdir = tempfile.mkdtemp() |
||||
|
|
||||
|
failed_files = [] |
||||
|
return_code = 0 |
||||
|
try: |
||||
|
task_queue = queue.Queue(args.jobs) |
||||
|
lock = threading.Lock() |
||||
|
for _ in range(args.jobs): |
||||
|
t = threading.Thread(target=run_tidy, |
||||
|
args=(args, tmpdir, task_queue, lock, failed_files)) |
||||
|
t.daemon = True |
||||
|
t.start() |
||||
|
|
||||
|
# Fill the queue with files. |
||||
|
with click.progressbar(files, width=30, file=sys.stderr, |
||||
|
item_show_func=progress_bar_show) as bar: |
||||
|
for name in bar: |
||||
|
task_queue.put(name) |
||||
|
|
||||
|
# Wait for all threads to be done. |
||||
|
task_queue.join() |
||||
|
return_code = len(failed_files) |
||||
|
|
||||
|
except KeyboardInterrupt: |
||||
|
print() |
||||
|
print('Ctrl-C detected, goodbye.') |
||||
|
if tmpdir: |
||||
|
shutil.rmtree(tmpdir) |
||||
|
os.kill(0, 9) |
||||
|
|
||||
|
print("finished") |
||||
|
if args.fix and failed_files: |
||||
|
print('Applying fixes ...') |
||||
|
try: |
||||
|
subprocess.call(['clang-apply-replacements-7', tmpdir]) |
||||
|
except: |
||||
|
print('Error applying fixes.\n', file=sys.stderr) |
||||
|
raise |
||||
|
|
||||
|
sys.exit(return_code) |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
main() |
||||
|
|
@ -0,0 +1,139 @@ |
|||||
|
import codecs |
||||
|
import json |
||||
|
import os.path |
||||
|
import re |
||||
|
import subprocess |
||||
|
import sys |
||||
|
|
||||
|
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) |
||||
|
basepath = os.path.join(root_path, 'src') |
||||
|
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') |
||||
|
|
||||
|
|
||||
|
def shlex_quote(s): |
||||
|
if not s: |
||||
|
return u"''" |
||||
|
if re.search(r'[^\w@%+=:,./-]', s) is None: |
||||
|
return s |
||||
|
|
||||
|
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" |
||||
|
|
||||
|
|
||||
|
def build_all_include(): |
||||
|
# Build a cpp file that includes all header files in this repo. |
||||
|
# Otherwise header-only integrations would not be tested by clang-tidy |
||||
|
headers = [] |
||||
|
for path in walk_files(basepath): |
||||
|
filetypes = ('.h',) |
||||
|
ext = os.path.splitext(path)[1] |
||||
|
if ext in filetypes: |
||||
|
path = os.path.relpath(path, root_path) |
||||
|
include_p = path.replace(os.path.sep, '/') |
||||
|
headers.append('#include "{}"'.format(include_p)) |
||||
|
headers.sort() |
||||
|
headers.append('') |
||||
|
content = '\n'.join(headers) |
||||
|
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f: |
||||
|
f.write(content) |
||||
|
|
||||
|
|
||||
|
def build_compile_commands(): |
||||
|
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json') |
||||
|
if not os.path.isfile(gcc_flags_json): |
||||
|
print("Could not find {} file which is required for clang-tidy.") |
||||
|
print('Please run "pio init --ide atom" in the root esphome folder to generate that file.') |
||||
|
sys.exit(1) |
||||
|
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f: |
||||
|
gcc_flags = json.load(f) |
||||
|
exec_path = gcc_flags['execPath'] |
||||
|
include_paths = gcc_flags['gccIncludePaths'].split(',') |
||||
|
includes = ['-isystem{}'.format(p) for p in include_paths] |
||||
|
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') |
||||
|
defines = [flag for flag in cpp_flags if flag.startswith('-D')] |
||||
|
command = [exec_path] |
||||
|
command.extend(includes) |
||||
|
command.extend(defines) |
||||
|
command.append('-std=gnu++11') |
||||
|
command.append('-Wall') |
||||
|
command.append('-Wno-delete-non-virtual-dtor') |
||||
|
command.append('-Wno-unused-variable') |
||||
|
command.append('-Wunreachable-code') |
||||
|
|
||||
|
source_files = [] |
||||
|
for path in walk_files(basepath): |
||||
|
filetypes = ('.cpp',) |
||||
|
ext = os.path.splitext(path)[1] |
||||
|
if ext in filetypes: |
||||
|
source_files.append(os.path.abspath(path)) |
||||
|
source_files.append(temp_header_file) |
||||
|
source_files.sort() |
||||
|
compile_commands = [{ |
||||
|
'directory': root_path, |
||||
|
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])), |
||||
|
'file': p |
||||
|
} for p in source_files] |
||||
|
compile_commands_json = os.path.join(root_path, 'compile_commands.json') |
||||
|
if os.path.isfile(compile_commands_json): |
||||
|
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f: |
||||
|
try: |
||||
|
if json.load(f) == compile_commands: |
||||
|
return |
||||
|
except: |
||||
|
pass |
||||
|
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f: |
||||
|
json.dump(compile_commands, f, indent=2) |
||||
|
|
||||
|
|
||||
|
def walk_files(path): |
||||
|
for root, _, files in os.walk(path): |
||||
|
for name in files: |
||||
|
yield os.path.join(root, name) |
||||
|
|
||||
|
|
||||
|
def get_output(*args): |
||||
|
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
||||
|
output, err = proc.communicate() |
||||
|
return output.decode('utf-8') |
||||
|
|
||||
|
|
||||
|
def splitlines_no_ends(string): |
||||
|
return [s.strip() for s in string.splitlines()] |
||||
|
|
||||
|
|
||||
|
def changed_files(): |
||||
|
for remote in ('upstream', 'origin'): |
||||
|
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD'] |
||||
|
try: |
||||
|
merge_base = splitlines_no_ends(get_output(*command))[0] |
||||
|
break |
||||
|
except: |
||||
|
pass |
||||
|
else: |
||||
|
raise ValueError("Git not configured") |
||||
|
command = ['git', 'diff', merge_base, '--name-only'] |
||||
|
changed = splitlines_no_ends(get_output(*command)) |
||||
|
changed = [os.path.relpath(f, os.getcwd()) for f in changed] |
||||
|
changed.sort() |
||||
|
return changed |
||||
|
|
||||
|
|
||||
|
def filter_changed(files): |
||||
|
changed = changed_files() |
||||
|
files = [f for f in files if f in changed] |
||||
|
print("Changed files:") |
||||
|
if not files: |
||||
|
print(" No changed files!") |
||||
|
for c in files: |
||||
|
print(" {}".format(c)) |
||||
|
return files |
||||
|
|
||||
|
|
||||
|
def git_ls_files(): |
||||
|
command = ['git', 'ls-files', '-s'] |
||||
|
proc = subprocess.Popen(command, stdout=subprocess.PIPE) |
||||
|
output, err = proc.communicate() |
||||
|
lines = [x.split() for x in output.decode('utf-8').splitlines()] |
||||
|
return { |
||||
|
s[3].strip(): int(s[0]) for s in lines |
||||
|
} |
||||
|
|
Binary file not shown.
Loading…
Reference in new issue