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