summaryrefslogtreecommitdiffstats
path: root/tools/scan-build-py/libscanbuild/command.py
blob: 69ca3393f955edaf3035d7051b4508520dc94899 (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
# -*- coding: utf-8 -*-
#                     The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
""" This module is responsible for to parse a compiler invocation. """

import re
import os

__all__ = ['Action', 'classify_parameters', 'classify_source']


class Action(object):
    """ Enumeration class for compiler action. """

    Link, Compile, Ignored = range(3)


def classify_parameters(command):
    """ Parses the command line arguments of the given invocation. """

    # result value of this method.
    # some value are preset, some will be set only when found.
    result = {
        'action': Action.Link,
        'files': [],
        'output': None,
        'compile_options': [],
        'c++': is_cplusplus_compiler(command[0])
        # archs_seen
        # language
    }

    # data structure to ignore compiler parameters.
    # key: parameter name, value: number of parameters to ignore afterwards.
    ignored = {
        '-g': 0,
        '-fsyntax-only': 0,
        '-save-temps': 0,
        '-install_name': 1,
        '-exported_symbols_list': 1,
        '-current_version': 1,
        '-compatibility_version': 1,
        '-init': 1,
        '-e': 1,
        '-seg1addr': 1,
        '-bundle_loader': 1,
        '-multiply_defined': 1,
        '-sectorder': 3,
        '--param': 1,
        '--serialize-diagnostics': 1
    }

    args = iter(command[1:])
    for arg in args:
        # compiler action parameters are the most important ones...
        if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
            result.update({'action': Action.Ignored})
        elif arg == '-c':
            result.update({'action': max(result['action'], Action.Compile)})
        # arch flags are taken...
        elif arg == '-arch':
            archs = result.get('archs_seen', [])
            result.update({'archs_seen': archs + [next(args)]})
        # explicit language option taken...
        elif arg == '-x':
            result.update({'language': next(args)})
        # output flag taken...
        elif arg == '-o':
            result.update({'output': next(args)})
        # warning disable options are taken...
        elif re.match(r'^-Wno-', arg):
            result['compile_options'].append(arg)
        # warning options are ignored...
        elif re.match(r'^-[mW].+', arg):
            pass
        # some preprocessor parameters are ignored...
        elif arg in {'-MD', '-MMD', '-MG', '-MP'}:
            pass
        elif arg in {'-MF', '-MT', '-MQ'}:
            next(args)
        # linker options are ignored...
        elif arg in {'-static', '-shared', '-s', '-rdynamic'} or \
                re.match(r'^-[lL].+', arg):
            pass
        elif arg in {'-l', '-L', '-u', '-z', '-T', '-Xlinker'}:
            next(args)
        # some other options are ignored...
        elif arg in ignored.keys():
            for _ in range(ignored[arg]):
                next(args)
        # parameters which looks source file are taken...
        elif re.match(r'^[^-].+', arg) and classify_source(arg):
            result['files'].append(arg)
        # and consider everything else as compile option.
        else:
            result['compile_options'].append(arg)

    return result


def classify_source(filename, cplusplus=False):
    """ Return the language from file name extension. """

    mapping = {
        '.c': 'c++' if cplusplus else 'c',
        '.i': 'c++-cpp-output' if cplusplus else 'c-cpp-output',
        '.ii': 'c++-cpp-output',
        '.m': 'objective-c',
        '.mi': 'objective-c-cpp-output',
        '.mm': 'objective-c++',
        '.mii': 'objective-c++-cpp-output',
        '.C': 'c++',
        '.cc': 'c++',
        '.CC': 'c++',
        '.cp': 'c++',
        '.cpp': 'c++',
        '.cxx': 'c++',
        '.c++': 'c++',
        '.C++': 'c++',
        '.txx': 'c++'
    }

    __, extension = os.path.splitext(os.path.basename(filename))
    return mapping.get(extension)


def is_cplusplus_compiler(name):
    """ Returns true when the compiler name refer to a C++ compiler. """

    match = re.match(r'^([^/]*/)*(\w*-)*(\w+\+\+)(-(\d+(\.\d+){0,3}))?$', name)
    return False if match is None else True