From 63c1f3114c351c76848308d30d2d9c37a41a9165 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 25 Oct 2018 12:06:26 +0200 Subject: gcc-check: clang-check compatible wrapper Perform syntax-only checks using a compilation database as produced by cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, similar to clang-check. --- gcc-check | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100755 gcc-check diff --git a/gcc-check b/gcc-check new file mode 100755 index 0000000..8a233cf --- /dev/null +++ b/gcc-check @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +""" +Analogous to clang-check, but for use with GCC. Intended use: + + cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 $srcdir + gcc-check path/to/source1.c path/to/source2.c + +Like clang-check, you can also add extra compiler options: + + gcc-check -extra-arg=-Wno-missing-prototypes path/to/source.c +""" +import argparse +import json +import os +import shlex +import subprocess +import sys + + +def llvm_abspath(path): + # Deliberately preserves ".." in path="../x" for LLVM compatibility. + return os.path.join(os.getcwd(), path) + + +# Make paths absolute (simplifying "a/../b" to "b") +#canonicalize_path = os.path.abspath +canonicalize_path = os.path.realpath + + +def locate_compilation_database(path): + path = llvm_abspath(path) + if not os.path.isdir(path): + path = os.path.dirname(path) + while True: + db_path = os.path.join(path, 'compile_commands.json') + if os.path.exists(db_path): + return db_path + if path == '/': + break + path = os.path.dirname(path) + print('Could not auto-detect compilation database', file=sys.stderr) + + +class DbEntry(object): + def __init__(self, obj): + def readstr(key): + value = obj.get(key) + if type(value) != str: + raise RuntimeError('Key %s not found' % key) + return value + self.directory = readstr('directory') + self.file = readstr('file') + self.command = readstr('command') + self.filepath = canonicalize_path(self.file) + + def __str__(self): + return '{"directory": %r, "command": %r, "file": %r}' % \ + (self.directory, self.command, self.file) + + def check(self, extra_arg_before=[], extra_arg=[]): + args = shlex.split(self.command) + is_cpp = args[0].endswith('++') + args[0] = ['gcc', 'g++'][is_cpp] + args += extra_arg_before + args = [arg for arg in args if self._filter_cc_arg(arg, is_cpp)] + args += extra_arg + args += ['-fsyntax-only'] + #print("Command: is_cpp=%r %r" % (is_cpp, args), file=sys.stderr) + subprocess.run(args, cwd=self.directory) + + def _filter_cc_arg(self, arg, is_cpp): + # List of Clang-specific options that are not supported by GCC. + # (as added by Wireshark) + common_args = [ + '-Qunused-arguments', + '-Wheader-guard', + '-Wcomma', + '-Wshorten-64-to-32', + '-Weverything', + '-Wno-documentation-unknown-command', + '-Wno-reserved-id-macro', + ] + c_args = [ + ] + cpp_args = [ + '-Wextra-semi', + ] + return arg not in common_args + (cpp_args if not is_cpp else c_args) + + +def parse_db(entries, db_path): + with open(db_path) as f: + arr = json.load(f) + if type(arr) != list: + raise RuntimeError('Parse Error: expected array') + for obj in arr: + entry = DbEntry(obj) + entries[entry.filepath] = entry + + +parser = argparse.ArgumentParser() +parser.add_argument('-p', metavar='string', dest='build_path', + help='Build path') +parser.add_argument('-extra-arg', metavar='string', dest='extra_arg', + action='append', default=[], + help='Additional argument to append to the compiler command line') +parser.add_argument('-extra-arg-before', metavar='string', + dest='extra_arg_before', + action='append', default=[], + help='Additional argument to prepend to the compiler command line') +parser.add_argument('sources', nargs='+') + + +def main(): + args = parser.parse_args() + sources = [canonicalize_path(path) for path in args.sources] + build_path = args.build_path + if not build_path or not os.path.exists(build_path): + build_path = llvm_abspath(args.sources[0]) + db_path = locate_compilation_database(build_path) + db = {} + if db_path: + parse_db(db, db_path) + for source_path in sources: + entry = db.get(source_path) + if entry is None: + print('Skipping %s. Compile command not found.' % source_path, + file=sys.stderr) + continue + entry.check(args.extra_arg_before, args.extra_arg) + + +if __name__ == '__main__': + main() -- cgit v1.2.1