diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 837dd47..76e1181 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,9 +45,12 @@ static_analysis: - apt -q update - apt install -qy build-essential - apt install -qy cppcheck + - apt install -qy clang-format clang-tidy + - pio init --ide atom script: # - cppcheck --enable=all --check-config src - cppcheck src + - run-clang-tidy -fix src # artifacts: # reports: # - diff --git a/script/build_compile_commands.py b/script/build_compile_commands.py new file mode 100755 index 0000000..9fcd30a --- /dev/null +++ b/script/build_compile_commands.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +import codecs +import json +import os.path +import re +import subprocess +import sys +import shlex + +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: + print(s) + 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 project 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 = shlex.split(gcc_flags['gccDefaultCppFlags']) + 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()