added clang-tidy

This commit is contained in:
proddy
2021-08-14 13:40:21 +02:00
parent 6c1bfccfaf
commit f9d768a7a7
7 changed files with 585 additions and 1 deletions

152
.clang-tidy Normal file
View File

@@ -0,0 +1,152 @@
---
Checks: >-
*,
-abseil-*,
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-mpi-*,
-objc-*,
-readability-braces-around-statements,
-readability-const-return-type,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
value: 'CamelCase'
- key: readability-identifier-naming.StructCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberPrefix
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.PrivateMethodPrefix
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
- key: readability-identifier-naming.FunctionCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodSuffix
value: '_'
- key: readability-identifier-naming.VirtualMethodCase
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''

4
.gitignore vendored
View File

@@ -25,4 +25,6 @@ emsesp
/interface/build
node_modules
/interface/.eslintcache
test.sh
test.sh
scripts/__pycache__
.temp

118
scripts/clang-format.py Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
# copied from esphome
# run from Linux using ./scripts/clang-forrmat.py
import argparse
import multiprocessing
import os
import queue
import re
import subprocess
import sys
import threading
import click
sys.path.append(os.path.dirname(__file__))
from helpers import get_output, src_files, filter_changed
def run_format(args, queue, lock, failed_files):
"""Takes filenames out of queue and runs clang-format on them."""
while True:
path = queue.get()
invocation = ['clang-format']
if args.inplace:
invocation.append('-i')
else:
invocation.extend(['--dry-run', '-Werror'])
invocation.append(path)
proc = subprocess.run(invocation, capture_output=True, encoding='utf-8')
if proc.returncode != 0:
with lock:
print()
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
print(proc.stdout)
print(proc.stderr)
print()
failed_files.append(path)
queue.task_done()
def progress_bar_show(value):
return value if value is not None else ''
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-j', '--jobs', type=int,
default=multiprocessing.cpu_count(),
help='number of format instances to be run in parallel.')
parser.add_argument('files', nargs='*', default=[],
help='files to be processed (regex on path)')
parser.add_argument('-i', '--inplace', action='store_true',
help='reformat files in-place')
parser.add_argument('-c', '--changed', action='store_true',
help='only run on changed files')
args = parser.parse_args()
try:
get_output('clang-format', '-version')
except:
print("""
Oops. It looks like clang-format is not installed.
Please check you can run "clang-format -version" in your terminal and install
clang-format (v11 or v12) if necessary.
Note you can also upload your code as a pull request on GitHub and see the CI check
output to apply clang-format.
""")
return 1
files = []
# all files
# for path in git_ls_files(['*.cpp', '*.h', '*.tcc']):
# files.append(os.path.relpath(path, os.getcwd()))
# just under src
for path in src_files(['.cpp', '.h']):
files.append(os.path.relpath(path, os.getcwd()))
if args.files:
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()
failed_files = []
try:
task_queue = queue.Queue(args.jobs)
lock = threading.Lock()
for _ in range(args.jobs):
t = threading.Thread(target=run_format,
args=(args, 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()
except KeyboardInterrupt:
print()
print('Ctrl-C detected, goodbye.')
os.kill(0, 9)
sys.exit(len(failed_files))
if __name__ == '__main__':
main()

190
scripts/clang-tidy.py Executable file
View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python3
# copied from esphome
# run from Linux using ./scripts/clang-forrmat.py
import argparse
import multiprocessing
import os
import queue
import shutil
import subprocess
import sys
import tempfile
import threading
import click
import pexpect
sys.path.append(os.path.dirname(__file__))
from helpers import shlex_quote, get_output, \
build_all_include, temp_header_file, filter_changed, load_idedata, src_files
def clang_options(idedata):
cmd = [
# target 32-bit arch (this prevents size mismatch errors on a 64-bit host)
'-m32',
# disable built-in include directories from the host
'-nostdinc',
'-nostdinc++',
# allow to condition code on the presence of clang-tidy
'-DCLANG_TIDY'
]
# copy compiler flags, except those clang doesn't understand.
cmd.extend(flag for flag in idedata['cxx_flags'].split(' ')
if flag not in ('-free', '-fipa-pta', '-mlongcalls', '-mtext-section-literals'))
# defines
cmd.extend(f'-D{define}' for define in idedata['defines'])
# add include directories, using -isystem for dependencies to suppress their errors
for directory in idedata['includes']['toolchain']:
cmd.extend(['-isystem', directory])
for directory in sorted(set(idedata['includes']['build'])):
dependency = "framework-arduino" in directory or "/libdeps/" in directory
cmd.extend(['-isystem' if dependency else '-I', directory])
return cmd
def run_tidy(args, options, tmpdir, queue, lock, failed_files):
while True:
path = queue.get()
invocation = ['clang-tidy']
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)
if args.quiet:
invocation.append('-quiet')
invocation.append(os.path.abspath(path))
invocation.append('--')
invocation.extend(options)
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)
if rc != 0:
with lock:
print()
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path))
print(output)
print()
failed_files.append(path)
queue.task_done()
def progress_bar_show(value):
if value is None:
return ''
def split_list(a, n):
k, m = divmod(len(a), n)
return [a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)]
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('--split-num', type=int, help='split the files into X jobs.',
default=None)
parser.add_argument('--split-at', type=int, help='which split is this? starts at 1',
default=None)
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', '-version')
except:
print("""
Oops. It looks like clang-tidy is not installed.
Please check you can run "clang-tidy -version" in your terminal and install
clang-tidy (v11 or v12) 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
idedata = load_idedata("clang-tidy")
options = clang_options(idedata)
files = []
for path in src_files(['.cpp']):
files.append(os.path.relpath(path, os.getcwd()))
if args.files:
# Match against files specified on command-line
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.split_num:
files = split_list(files, args.split_num)[args.split_at - 1]
if args.all_headers and args.split_at in (None, 1):
build_all_include()
files.insert(0, temp_header_file)
tmpdir = None
if args.fix:
tmpdir = tempfile.mkdtemp()
failed_files = []
try:
task_queue = queue.Queue(args.jobs)
lock = threading.Lock()
for _ in range(args.jobs):
t = threading.Thread(target=run_tidy,
args=(args, options, 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()
except KeyboardInterrupt:
print()
print('Ctrl-C detected, goodbye.')
if tmpdir:
shutil.rmtree(tmpdir)
os.kill(0, 9)
if args.fix and failed_files:
print('Applying fixes ...')
try:
subprocess.call(['clang-apply-replacements-12', tmpdir])
except:
print('Error applying fixes.\n', file=sys.stderr)
raise
sys.exit(len(failed_files))
if __name__ == '__main__':
main()

122
scripts/helpers.py Executable file
View File

@@ -0,0 +1,122 @@
import os.path
import re
import subprocess
import json
from pathlib import Path
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", "..")))
basepath = os.path.join(root_path, "src")
temp_folder = os.path.join(root_path, ".temp")
temp_header_file = os.path.join(temp_folder, "all-include.cpp")
def shlex_quote(s):
if not s:
return "''"
if re.search(r"[^\w@%+=:,./-]", s) is None:
return s
return "'" + s.replace("'", "'\"'\"'") + "'"
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(f'#include "{include_p}"')
headers.sort()
headers.append("")
content = "\n".join(headers)
p = Path(temp_header_file)
p.parent.mkdir(exist_ok=True)
p.write_text(content)
def src_files(filetypes=None):
file_list = []
for path in walk_files(basepath):
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, root_path)
file_list.append(path)
return file_list
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 get_err(*args):
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = proc.communicate()
return err.decode("utf-8")
def splitlines_no_ends(string):
return [s.strip() for s in string.splitlines()]
def changed_files():
check_remotes = ["upstream", "origin"]
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
for remote in check_remotes:
command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "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(f" {c}")
return files
def git_ls_files(patterns=None):
command = ["git", "ls-files", "-s"]
if patterns is not None:
command.extend(patterns)
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}
def load_idedata(environment):
platformio_ini = Path(root_path) / "platformio.ini"
temp_idedata = Path(temp_folder) / f"idedata-{environment}.json"
if not platformio_ini.is_file() or not temp_idedata.is_file():
changed = True
elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime:
changed = True
else:
changed = False
if not changed:
return json.loads(temp_idedata.read_text())
stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment])
match = re.search(r'{\s*".*}', stdout.decode("utf-8"))
data = json.loads(match.group())
temp_idedata.parent.mkdir(exist_ok=True)
temp_idedata.write_text(json.dumps(data, indent=2) + "\n")
return data

0
scripts/rename_fw.py Normal file → Executable file
View File

0
scripts/upload_fw.py Normal file → Executable file
View File