#!/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()