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