1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of Qt for Python.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28
29 #include <abstractmetabuilder_p.h>
30 #include <parser/codemodel.h>
31 #include <clangparser/compilersupport.h>
32
33 #include <QtCore/QCoreApplication>
34 #include <QtCore/QCommandLineOption>
35 #include <QtCore/QCommandLineParser>
36 #include <QtCore/QDateTime>
37 #include <QtCore/QDebug>
38 #include <QtCore/QDir>
39 #include <QtCore/QFile>
40 #include <QtCore/QXmlStreamWriter>
41
42 #include <iostream>
43 #include <algorithm>
44 #include <iterator>
45
46 static bool optJoinNamespaces = false;
47
languageLevelDescription()48 static inline QString languageLevelDescription()
49 {
50 return QLatin1String("C++ Language level (c++11..c++17, default=")
51 + QLatin1String(clang::languageLevelOption(clang::emulatedCompilerLanguageLevel()))
52 + QLatin1Char(')');
53 }
54
formatDebugOutput(const FileModelItem & dom,bool verbose)55 static void formatDebugOutput(const FileModelItem &dom, bool verbose)
56 {
57 QString output;
58 {
59 QDebug debug(&output);
60 if (verbose)
61 debug.setVerbosity(3);
62 debug << dom.data();
63 }
64 std::cout << qPrintable(output) << '\n';
65 }
66
67 static const char *primitiveTypes[] = {
68 "int", "unsigned", "short", "unsigned short", "long", "unsigned long",
69 "float", "double"
70 };
71
nameAttribute()72 static inline QString nameAttribute() { return QStringLiteral("name"); }
73
74 static void formatXmlClass(QXmlStreamWriter &writer, const ClassModelItem &klass);
75
formatXmlEnum(QXmlStreamWriter & writer,const EnumModelItem & en)76 static void formatXmlEnum(QXmlStreamWriter &writer, const EnumModelItem &en)
77 {
78 writer.writeStartElement(QStringLiteral("enum-type"));
79 writer.writeAttribute(nameAttribute(), en->name());
80 writer.writeEndElement();
81 }
82
useClass(const ClassModelItem & c)83 static bool useClass(const ClassModelItem &c)
84 {
85 return c->classType() != CodeModel::Union && c->templateParameters().isEmpty()
86 && !c->name().isEmpty(); // No anonymous structs
87 }
88
formatXmlScopeMembers(QXmlStreamWriter & writer,const ScopeModelItem & nsp)89 static void formatXmlScopeMembers(QXmlStreamWriter &writer, const ScopeModelItem &nsp)
90 {
91 for (const auto &klass : nsp->classes()) {
92 if (useClass(klass))
93 formatXmlClass(writer, klass);
94 }
95 for (const auto &en : nsp->enums())
96 formatXmlEnum(writer, en);
97 }
98
isPublicCopyConstructor(const FunctionModelItem & f)99 static bool isPublicCopyConstructor(const FunctionModelItem &f)
100 {
101 return f->functionType() == CodeModel::CopyConstructor
102 && f->accessPolicy() == CodeModel::Public && !f->isDeleted();
103 }
104
formatXmlLocationComment(QXmlStreamWriter & writer,const CodeModelItem & i)105 static void formatXmlLocationComment(QXmlStreamWriter &writer, const CodeModelItem &i)
106 {
107 QString comment;
108 QTextStream(&comment) << ' ' << i->fileName() << ':' << i->startLine() << ' ';
109 writer.writeComment(comment);
110 }
111
formatXmlClass(QXmlStreamWriter & writer,const ClassModelItem & klass)112 static void formatXmlClass(QXmlStreamWriter &writer, const ClassModelItem &klass)
113 {
114 // Heuristics for value types: check on public copy constructors.
115 const auto functions = klass->functions();
116 const bool isValueType = std::any_of(functions.cbegin(), functions.cend(),
117 isPublicCopyConstructor);
118 formatXmlLocationComment(writer, klass);
119 writer.writeStartElement(isValueType ? QStringLiteral("value-type")
120 : QStringLiteral("object-type"));
121 writer.writeAttribute(nameAttribute(), klass->name());
122 formatXmlScopeMembers(writer, klass);
123 writer.writeEndElement();
124 }
125
126 // Check whether a namespace is relevant for type system
127 // output, that is, has non template classes, functions or enumerations.
hasMembers(const NamespaceModelItem & nsp)128 static bool hasMembers(const NamespaceModelItem &nsp)
129 {
130 if (!nsp->namespaces().isEmpty() || !nsp->enums().isEmpty()
131 || !nsp->functions().isEmpty()) {
132 return true;
133 }
134 const auto classes = nsp->classes();
135 return std::any_of(classes.cbegin(), classes.cend(), useClass);
136 }
137
startXmlNamespace(QXmlStreamWriter & writer,const NamespaceModelItem & nsp)138 static void startXmlNamespace(QXmlStreamWriter &writer, const NamespaceModelItem &nsp)
139 {
140 formatXmlLocationComment(writer, nsp);
141 writer.writeStartElement(QStringLiteral("namespace-type"));
142 writer.writeAttribute(nameAttribute(), nsp->name());
143 }
144
formatXmlNamespaceMembers(QXmlStreamWriter & writer,const NamespaceModelItem & nsp)145 static void formatXmlNamespaceMembers(QXmlStreamWriter &writer, const NamespaceModelItem &nsp)
146 {
147 auto nestedNamespaces = nsp->namespaces();
148 for (int i = nestedNamespaces.size() - 1; i >= 0; --i) {
149 if (!hasMembers(nestedNamespaces.at(i)))
150 nestedNamespaces.removeAt(i);
151 }
152 while (!nestedNamespaces.isEmpty()) {
153 auto current = nestedNamespaces.takeFirst();
154 startXmlNamespace(writer, current);
155 formatXmlNamespaceMembers(writer, current);
156 if (optJoinNamespaces) {
157 // Write out members of identical namespaces and remove
158 const QString name = current->name();
159 for (int i = 0; i < nestedNamespaces.size(); ) {
160 if (nestedNamespaces.at(i)->name() == name) {
161 formatXmlNamespaceMembers(writer, nestedNamespaces.at(i));
162 nestedNamespaces.removeAt(i);
163 } else {
164 ++i;
165 }
166 }
167 }
168 writer.writeEndElement();
169 }
170
171 for (auto func : nsp->functions()) {
172 const QString signature = func->typeSystemSignature();
173 if (!signature.contains(QLatin1String("operator"))) { // Skip free operators
174 writer.writeStartElement(QStringLiteral("function"));
175 writer.writeAttribute(QStringLiteral("signature"), signature);
176 writer.writeEndElement();
177 }
178 }
179 formatXmlScopeMembers(writer, nsp);
180 }
181
formatXmlOutput(const FileModelItem & dom)182 static void formatXmlOutput(const FileModelItem &dom)
183 {
184 QString output;
185 QXmlStreamWriter writer(&output);
186 writer.setAutoFormatting(true);
187 writer.writeStartDocument();
188 writer.writeStartElement(QStringLiteral("typesystem"));
189 writer.writeAttribute(QStringLiteral("package"), QStringLiteral("insert_name"));
190 writer.writeComment(QStringLiteral("Auto-generated ") +
191 QDateTime::currentDateTime().toString(Qt::ISODate));
192 for (auto p : primitiveTypes) {
193 writer.writeStartElement(QStringLiteral("primitive-type"));
194 writer.writeAttribute(nameAttribute(), QLatin1String(p));
195 writer.writeEndElement();
196 }
197 formatXmlNamespaceMembers(writer, dom);
198 writer.writeEndElement();
199 writer.writeEndDocument();
200 std::cout << qPrintable(output) << '\n';
201 }
202
203 static const char descriptionFormat[] = R"(
204 Type system dumper
205
206 Parses a C++ header and dumps out the classes found in typesystem XML syntax.
207 Arguments are arguments to the compiler the last of which should be the header
208 or source file.
209 It is recommended to create a .hh include file including the desired headers
210 and pass that along with the required include paths.
211
212 Based on Qt %1 and LibClang v%2.)";
213
main(int argc,char ** argv)214 int main(int argc, char **argv)
215 {
216 QCoreApplication app(argc, argv);
217
218 QCommandLineParser parser;
219 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
220 parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
221 const QString description =
222 QString::fromLatin1(descriptionFormat).arg(QLatin1String(qVersion()),
223 clang::libClangVersion().toString());
224 parser.setApplicationDescription(description);
225 parser.addHelpOption();
226 parser.addVersionOption();
227 QCommandLineOption verboseOption(QStringLiteral("verbose"),
228 QStringLiteral("Display verbose output about types"));
229 parser.addOption(verboseOption);
230 QCommandLineOption debugOption(QStringLiteral("debug"),
231 QStringLiteral("Display debug output"));
232 parser.addOption(debugOption);
233
234 QCommandLineOption joinNamespacesOption({QStringLiteral("j"), QStringLiteral("join-namespaces")},
235 QStringLiteral("Join namespaces"));
236 parser.addOption(joinNamespacesOption);
237
238 QCommandLineOption languageLevelOption(QStringLiteral("std"),
239 languageLevelDescription(),
240 QStringLiteral("level"));
241 parser.addOption(languageLevelOption);
242 parser.addPositionalArgument(QStringLiteral("argument"),
243 QStringLiteral("C++ compiler argument"),
244 QStringLiteral("argument(s)"));
245
246 parser.process(app);
247 const QStringList &positionalArguments = parser.positionalArguments();
248 if (positionalArguments.isEmpty())
249 parser.showHelp(1);
250
251 QByteArrayList arguments;
252 std::transform(positionalArguments.cbegin(), positionalArguments.cend(),
253 std::back_inserter(arguments), QFile::encodeName);
254
255 LanguageLevel level = LanguageLevel::Default;
256 if (parser.isSet(languageLevelOption)) {
257 const QByteArray value = parser.value(languageLevelOption).toLatin1();
258 level = clang::languageLevelFromOption(value.constData());
259 if (level == LanguageLevel::Default) {
260 std::cerr << "Invalid value \"" << value.constData()
261 << "\" for language level option.\n";
262 return -2;
263 }
264 }
265
266 optJoinNamespaces = parser.isSet(joinNamespacesOption);
267
268 const FileModelItem dom = AbstractMetaBuilderPrivate::buildDom(arguments, level, 0);
269 if (dom.isNull()) {
270 QString message = QLatin1String("Unable to parse ") + positionalArguments.join(QLatin1Char(' '));
271 std::cerr << qPrintable(message) << '\n';
272 return -2;
273 }
274
275 if (parser.isSet(debugOption))
276 formatDebugOutput(dom, parser.isSet(verboseOption));
277 else
278 formatXmlOutput(dom);
279
280 return 0;
281 }
282