summaryrefslogtreecommitdiff
path: root/gcc-check
blob: 0b97922638d9d789911b60b9445aca517215cb61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/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',
            # my custom flags
            '-fuse-ld=lld',
        ]
        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()