summaryrefslogtreecommitdiffstats
path: root/mapgen/iwyu-mapgen-qt.py
blob: 0fca4ad8cfc304c38819469bedc3e607bcf9426a (plain) (blame)
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python

##===--- iwyu-mapgen-qt.py ------------------------------------------------===##
#
#                     The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##

""" Generates mappings for Qt API headers.

Qt has quite a strong module convention, where there's one public header for
every module class, e.g.:

- For a module X there's typically...
- ... a set of classes called QtXy, QtXyz...
- ... and a corresponding public header for each called QtXy, QtXyz...
- ... and possibly a set of private headers called qtxy.h, qtxyz.h...

Use these conventions to generate symbol and include mappings for the entire Qt
tree.
"""

import argparse
import glob
import json
import os
import re
import sys


OUTFILEHDR = ("# Do not edit! This file was generated by the script %s." %
              os.path.basename(__file__))

QOBJECT_SYMBOLS = [
    "QObjectList",
    "qFindChildren",
    "qobject_cast",
    "QT_NO_NARROWING_CONVERSIONS_IN_CONNECT",
    "Q_CLASSINFO",
    "Q_DISABLE_COPY",
    "Q_DISABLE_COPY_MOVE",
    "Q_DISABLE_MOVE",
    "Q_EMIT",
    "Q_ENUM",
    "Q_ENUM_NS",
    "Q_FLAG",
    "Q_FLAG_NS",
    "Q_GADGET",
    "Q_INTERFACES",
    "Q_INVOKABLE",
    "Q_NAMESPACE",
    "Q_NAMESPACE_EXPORT",
    "Q_OBJECT",
    "Q_PROPERTY",
    "Q_REVISION",
    "Q_SET_OBJECT_NAME",
    "Q_SIGNAL",
    "Q_SIGNALS",
    "Q_SLOT",
    "Q_SLOTS",
    "emit",
    "slots",
    "signals",
    "SIGNAL",
    "SLOT",
]


class QtHeader(object):
    """ Carry data associated with a Qt header """
    def __init__(self, headername):
        self.headername = headername
        self.classname = os.path.basename(headername)
        self.modulename = os.path.basename(os.path.dirname(headername))
        self._private_headers = None

    def get_private_headers(self):
        """ Return a list of headernames included by this header """
        if self._private_headers is None:
            with open(self.headername, 'r') as headerfile:
                included = re.findall(r'#include "(.*)\.h"', headerfile.read())
            self._private_headers = list(included)
        return self._private_headers


def generate_imp_lines(symbols_map, includes_map):
    """ Generate json-formatted strings in .imp format.

    This should ideally return a jsonable structure instead, and use json.dump
    to write it to the output file directly. But there doesn't seem to be a
    simple way to convince Python's json library to generate a "packed"
    formatting, it always prefers to wrap dicts onto multiple lines.

    Cheat, and use json.dumps for escaping and build a string instead.
    """
    def jsonline(mapping, indent):
        return (indent * " ") + json.dumps(mapping)

    for symbol, header in symbols_map:
        map_to = "<" + header + ">"
        yield jsonline({"symbol": [symbol, "private", map_to, "public"]},
                       indent=2)

    for module, include, header in includes_map:
        # Use regex map-from to match both quoted and angled includes and
        # optional directory prefix (e.g. <QtCore/qnamespace.h> is equivalent to
        # "qnamespace.h").
        map_from = r'@["<](%s/)?%s\.h[">]' % (module, include)
        map_to = "<" + header + ">"
        yield jsonline({"include": [map_from, "private", map_to, "public"]},
                       indent=2)


def add_mapping_rules(header, symbols_map, includes_map):
    """ Add symbol and include mappings for a Qt module. """
    symbols_map += [(header.classname, header.classname)]
    for include in header.get_private_headers():
        includes_map += [(header.modulename, include, header.classname)]


def main(qtroot):
    """ Entry point. """
    symbols_map = []
    includes_map = []
    deferred_headers = []

    # Add manual overrides.
    symbols_map += [("qDebug", "QtGlobal")]
    symbols_map += [(symbol, "QObject") for symbol in QOBJECT_SYMBOLS]
    includes_map += [("QtCore", "qnamespace", "Qt")]

    # Collect mapping information from Qt directory tree.
    headers = glob.glob(os.path.join(qtroot, '**/*[!.h]'))
    for header in headers:
        if os.path.isdir(header):
            continue

        header = QtHeader(header)
        if header.classname == "QInternal":
            continue

        if header.classname == header.modulename:
            deferred_headers.append(header)
        else:
            add_mapping_rules(header, symbols_map, includes_map)

    for header in deferred_headers:
        add_mapping_rules(header, symbols_map, includes_map)

    # Print mappings
    print(OUTFILEHDR)
    print("[")
    print(",\n".join(generate_imp_lines(symbols_map, includes_map)))
    print("]")
    return 0


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("qtroot",
                        help="Qt include root (e.g. /usr/include/.../qt5)")
    args = parser.parse_args()
    sys.exit(main(args.qtroot))