qmlplugindump: Fix dumping of extended QGraphicsWidget.
[profile/ivi/qtdeclarative.git] / tools / qmlplugindump / main.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the tools applications of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QtDeclarative/qdeclarativeengine.h>
43 #include <QtDeclarative/private/qdeclarativemetatype_p.h>
44 #include <QtDeclarative/private/qdeclarativeopenmetaobject_p.h>
45 #include <QtQuick/private/qquickevents_p_p.h>
46 #include <QtQuick/private/qquickpincharea_p.h>
47
48 #include <QtWidgets/QApplication>
49
50 #include <QtCore/QDir>
51 #include <QtCore/QFileInfo>
52 #include <QtCore/QSet>
53 #include <QtCore/QStringList>
54 #include <QtCore/QTimer>
55 #include <QtCore/QMetaObject>
56 #include <QtCore/QMetaProperty>
57 #include <QtCore/QDebug>
58 #include <QtCore/private/qobject_p.h>
59 #include <QtCore/private/qmetaobject_p.h>
60
61 #include <iostream>
62
63 #include "qmlstreamwriter.h"
64
65 #ifdef QT_SIMULATOR
66 #include <QtGui/private/qsimulatorconnection_p.h>
67 #endif
68
69 #ifdef Q_OS_UNIX
70 #include <signal.h>
71 #endif
72
73 QString pluginImportPath;
74 bool verbose = false;
75
76 QString currentProperty;
77 QString inObjectInstantiation;
78
79 void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, bool extended = false)
80 {
81     if (! meta || metas->contains(meta))
82         return;
83
84     // dynamic meta objects can break things badly (like QDeclarative1VisualDataModelParts)
85     // but extended types are usually fine (like QDeclarative1GraphicsWidget)
86     const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data);
87     if (extended || !(mop->flags & DynamicMetaObject))
88         metas->insert(meta);
89
90     collectReachableMetaObjects(meta->superClass(), metas);
91 }
92
93 void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas)
94 {
95     if (! object)
96         return;
97
98     const QMetaObject *meta = object->metaObject();
99     if (verbose)
100         qDebug() << "Processing object" << meta->className();
101     collectReachableMetaObjects(meta, metas);
102
103     for (int index = 0; index < meta->propertyCount(); ++index) {
104         QMetaProperty prop = meta->property(index);
105         if (QDeclarativeMetaType::isQObject(prop.userType())) {
106             if (verbose)
107                 qDebug() << "  Processing property" << prop.name();
108             currentProperty = QString("%1::%2").arg(meta->className(), prop.name());
109
110             // if the property was not initialized during construction,
111             // accessing a member of oo is going to cause a segmentation fault
112             QObject *oo = QDeclarativeMetaType::toQObject(prop.read(object));
113             if (oo && !metas->contains(oo->metaObject()))
114                 collectReachableMetaObjects(oo, metas);
115             currentProperty.clear();
116         }
117     }
118 }
119
120 void collectReachableMetaObjects(const QDeclarativeType *ty, QSet<const QMetaObject *> *metas)
121 {
122     collectReachableMetaObjects(ty->metaObject(), metas, ty->isExtendedType());
123     if (ty->attachedPropertiesType())
124         collectReachableMetaObjects(ty->attachedPropertiesType(), metas);
125 }
126
127 /* We want to add the MetaObject for 'Qt' to the list, this is a
128    simple way to access it.
129 */
130 class FriendlyQObject: public QObject
131 {
132 public:
133     static const QMetaObject *qtMeta() { return &staticQtMetaObject; }
134 };
135
136 /* When we dump a QMetaObject, we want to list all the types it is exported as.
137    To do this, we need to find the QDeclarativeTypes associated with this
138    QMetaObject.
139 */
140 static QHash<QByteArray, QSet<const QDeclarativeType *> > qmlTypesByCppName;
141
142 static QHash<QByteArray, QByteArray> cppToId;
143
144 /* Takes a C++ type name, such as Qt::LayoutDirection or QString and
145    maps it to how it should appear in the description file.
146
147    These names need to be unique globally, so we don't change the C++ symbol's
148    name much. It is mostly used to for explicit translations such as
149    QString->string and translations for extended QML objects.
150 */
151 QByteArray convertToId(const QByteArray &cppName)
152 {
153     return cppToId.value(cppName, cppName);
154 }
155
156 QByteArray convertToId(const QMetaObject *mo)
157 {
158     QByteArray className(mo->className());
159     if (!className.isEmpty())
160         return convertToId(className);
161
162     // likely a metaobject generated for an extended qml object
163     if (mo->superClass()) {
164         className = convertToId(mo->superClass());
165         className.append("_extended");
166         return className;
167     }
168
169     static QHash<const QMetaObject *, QByteArray> generatedNames;
170     className = generatedNames.value(mo);
171     if (!className.isEmpty())
172         return className;
173
174     qWarning() << "Found a QMetaObject without a className, generating a random name";
175     className = QByteArray("error-unknown-name-");
176     className.append(QByteArray::number(generatedNames.size()));
177     generatedNames.insert(mo, className);
178     return className;
179 }
180
181 /* All exported module APIs are collected into this list */
182 class ModuleApi {
183 public:
184     QString uri;
185     int majorVersion;
186     int minorVersion;
187     QByteArray objectId;
188 };
189 QList<ModuleApi> moduleApis;
190
191 QSet<const QMetaObject *> collectReachableMetaObjects(QDeclarativeEngine *engine, const QList<QDeclarativeType *> &skip = QList<QDeclarativeType *>())
192 {
193     QSet<const QMetaObject *> metas;
194     metas.insert(FriendlyQObject::qtMeta());
195
196     QHash<QByteArray, QSet<QByteArray> > extensions;
197     foreach (const QDeclarativeType *ty, QDeclarativeMetaType::qmlTypes()) {
198         qmlTypesByCppName[ty->metaObject()->className()].insert(ty);
199         if (ty->isExtendedType()) {
200             extensions[ty->typeName()].insert(ty->metaObject()->className());
201         }
202         collectReachableMetaObjects(ty, &metas);
203     }
204
205     // Adjust exports of the base object if there are extensions.
206     // For each export of a base object there can be a single extension object overriding it.
207     // Example: QDeclarativeGraphicsWidget overrides the QtQuick/QGraphicsWidget export
208     //          of QGraphicsWidget.
209     foreach (const QByteArray &baseCpp, extensions.keys()) {
210         QSet<const QDeclarativeType *> baseExports = qmlTypesByCppName.value(baseCpp);
211
212         const QSet<QByteArray> extensionCppNames = extensions.value(baseCpp);
213         foreach (const QByteArray &extensionCppName, extensionCppNames) {
214             const QSet<const QDeclarativeType *> extensionExports = qmlTypesByCppName.value(extensionCppName);
215
216             // remove extension exports from base imports
217             // unfortunately the QDeclarativeType pointers don't match, so can't use QSet::substract
218             QSet<const QDeclarativeType *> newBaseExports;
219             foreach (const QDeclarativeType *baseExport, baseExports) {
220                 bool match = false;
221                 foreach (const QDeclarativeType *extensionExport, extensionExports) {
222                     if (baseExport->qmlTypeName() == extensionExport->qmlTypeName()
223                             && baseExport->majorVersion() == extensionExport->majorVersion()
224                             && baseExport->minorVersion() == extensionExport->minorVersion()) {
225                         match = true;
226                         break;
227                     }
228                 }
229                 if (!match)
230                     newBaseExports.insert(baseExport);
231             }
232             baseExports = newBaseExports;
233         }
234         qmlTypesByCppName[baseCpp] = baseExports;
235     }
236
237     // find even more QMetaObjects by instantiating QML types and running
238     // over the instances
239     foreach (QDeclarativeType *ty, QDeclarativeMetaType::qmlTypes()) {
240         if (skip.contains(ty))
241             continue;
242         if (ty->isExtendedType())
243             continue;
244         if (!ty->isCreatable())
245             continue;
246         if (ty->typeName() == "QDeclarativeComponent")
247             continue;
248
249         QString tyName = ty->qmlTypeName();
250         tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1);
251         if (tyName.isEmpty())
252             continue;
253
254         inObjectInstantiation = tyName;
255         QObject *object = ty->create();
256         inObjectInstantiation.clear();
257
258         if (object)
259             collectReachableMetaObjects(object, &metas);
260         else
261             qWarning() << "Could not create" << tyName;
262     }
263
264     // extract exported module api
265     QHashIterator<QString, QList<QDeclarativeMetaType::ModuleApi> > moduleApiIt(QDeclarativeMetaType::moduleApis());
266     while (moduleApiIt.hasNext()) {
267         moduleApiIt.next();
268         foreach (const QDeclarativeMetaType::ModuleApi &api, moduleApiIt.value()) {
269             ModuleApi moduleApi;
270             moduleApi.uri = moduleApiIt.key();
271             moduleApi.majorVersion = api.major;
272             moduleApi.minorVersion = api.minor;
273
274             if (api.qobject) {
275                 if (QObject *object = (*api.qobject)(engine, engine)) {
276                     collectReachableMetaObjects(object, &metas);
277                     moduleApi.objectId = convertToId(object->metaObject()->className());
278                     delete object;
279                 }
280             } else if (api.script) {
281                 qWarning() << "Can't dump the module api in " << moduleApi.uri << ". QJSValue based module API is not supported.";
282 //                QJSValue value = (*api.script)(engine, engine);
283 //                IdToObjectHash jsObjects;
284 //                collectReachableJSObjects(value, &jsObjects, &metas);
285             }
286
287             moduleApis += moduleApi;
288         }
289     }
290
291     return metas;
292 }
293
294
295 class Dumper
296 {
297     QmlStreamWriter *qml;
298     QString relocatableModuleUri;
299
300 public:
301     Dumper(QmlStreamWriter *qml) : qml(qml) {}
302
303     void setRelocatableModuleUri(const QString &uri)
304     {
305         relocatableModuleUri = uri;
306     }
307
308     void dump(const QMetaObject *meta)
309     {
310         qml->writeStartObject("Component");
311
312         QByteArray id = convertToId(meta);
313         qml->writeScriptBinding(QLatin1String("name"), enquote(id));
314
315         for (int index = meta->classInfoCount() - 1 ; index >= 0 ; --index) {
316             QMetaClassInfo classInfo = meta->classInfo(index);
317             if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
318                 qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value())));
319                 break;
320             }
321         }
322
323         if (meta->superClass())
324             qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass())));
325
326         QSet<const QDeclarativeType *> qmlTypes = qmlTypesByCppName.value(meta->className());
327         if (!qmlTypes.isEmpty()) {
328             QHash<QString, const QDeclarativeType *> exports;
329
330             foreach (const QDeclarativeType *qmlTy, qmlTypes) {
331                 QString qmlTyName = qmlTy->qmlTypeName();
332                 if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) {
333                     qmlTyName.remove(0, relocatableModuleUri.size() + 1);
334                 }
335                 if (qmlTyName.startsWith("./")) {
336                     qmlTyName.remove(0, 2);
337                 }
338                 if (qmlTyName.startsWith("/")) {
339                     qmlTyName.remove(0, 1);
340                 }
341                 const QString exportString = enquote(
342                             QString("%1 %2.%3").arg(
343                                 qmlTyName,
344                                 QString::number(qmlTy->majorVersion()),
345                                 QString::number(qmlTy->minorVersion())));
346                 exports.insert(exportString, qmlTy);
347             }
348
349             // ensure exports are sorted and don't change order when the plugin is dumped again
350             QStringList exportStrings = exports.keys();
351             qSort(exportStrings);
352             qml->writeArrayBinding(QLatin1String("exports"), exportStrings);
353
354             // write meta object revisions unless they're all zero
355             QStringList metaObjectRevisions;
356             bool shouldWriteMetaObjectRevisions = false;
357             foreach (const QString &exportString, exportStrings) {
358                 int metaObjectRevision = exports[exportString]->metaObjectRevision();
359                 if (metaObjectRevision != 0)
360                     shouldWriteMetaObjectRevisions = true;
361                 metaObjectRevisions += QString::number(metaObjectRevision);
362             }
363             if (shouldWriteMetaObjectRevisions)
364                 qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions);
365
366             if (const QMetaObject *attachedType = (*qmlTypes.begin())->attachedPropertiesType()) {
367                 // Can happen when a type is registered that returns itself as attachedPropertiesType()
368                 // because there is no creatable type to attach to.
369                 if (attachedType != meta) {
370                     qml->writeScriptBinding(QLatin1String("attachedType"), enquote(
371                                                 convertToId(attachedType)));
372                 }
373             }
374         }
375
376         for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index)
377             dump(meta->enumerator(index));
378
379         QSet<QString> implicitSignals;
380         for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) {
381             const QMetaProperty &property = meta->property(index);
382             dump(property);
383             implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name())));
384         }
385
386         if (meta == &QObject::staticMetaObject) {
387             // for QObject, hide deleteLater() and onDestroyed
388             for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) {
389                 QMetaMethod method = meta->method(index);
390                 const char *signature(method.signature());
391                 if (signature == QLatin1String("destroyed(QObject*)")
392                         || signature == QLatin1String("destroyed()")
393                         || signature == QLatin1String("deleteLater()"))
394                     continue;
395                 dump(method, implicitSignals);
396             }
397
398             // and add toString(), destroy() and destroy(int)
399             qml->writeStartObject(QLatin1String("Method"));
400             qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString")));
401             qml->writeEndObject();
402             qml->writeStartObject(QLatin1String("Method"));
403             qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
404             qml->writeEndObject();
405             qml->writeStartObject(QLatin1String("Method"));
406             qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
407             qml->writeStartObject(QLatin1String("Parameter"));
408             qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay")));
409             qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int")));
410             qml->writeEndObject();
411             qml->writeEndObject();
412         } else {
413             for (int index = meta->methodOffset(); index < meta->methodCount(); ++index)
414                 dump(meta->method(index), implicitSignals);
415         }
416
417         qml->writeEndObject();
418     }
419
420     void dump(const ModuleApi &api)
421     {
422         qml->writeStartObject(QLatin1String("ModuleApi"));
423         if (api.uri != relocatableModuleUri)
424             qml->writeScriptBinding(QLatin1String("uri"), enquote(api.uri));
425         qml->writeScriptBinding(QLatin1String("version"), QString("%1.%2").arg(
426                                     QString::number(api.majorVersion),
427                                     QString::number(api.minorVersion)));
428         qml->writeScriptBinding(QLatin1String("name"), enquote(api.objectId));
429         qml->writeEndObject();
430     }
431
432     void writeEasingCurve()
433     {
434         qml->writeStartObject(QLatin1String("Component"));
435         qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve")));
436         qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QDeclarativeEasingValueType")));
437         qml->writeEndObject();
438     }
439
440 private:
441     static QString enquote(const QString &string)
442     {
443         return QString("\"%1\"").arg(string);
444     }
445
446     /* Removes pointer and list annotations from a type name, returning
447        what was removed in isList and isPointer
448     */
449     static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer)
450     {
451         static QByteArray declListPrefix = "QDeclarativeListProperty<";
452
453         if (typeName->endsWith('*')) {
454             *isPointer = true;
455             typeName->truncate(typeName->length() - 1);
456             removePointerAndList(typeName, isList, isPointer);
457         } else if (typeName->startsWith(declListPrefix)) {
458             *isList = true;
459             typeName->truncate(typeName->length() - 1); // get rid of the suffix '>'
460             *typeName = typeName->mid(declListPrefix.size());
461             removePointerAndList(typeName, isList, isPointer);
462         }
463
464         *typeName = convertToId(*typeName);
465     }
466
467     void writeTypeProperties(QByteArray typeName, bool isWritable)
468     {
469         bool isList = false, isPointer = false;
470         removePointerAndList(&typeName, &isList, &isPointer);
471
472         qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
473         if (isList)
474             qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true"));
475         if (!isWritable)
476             qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true"));
477         if (isPointer)
478             qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true"));
479     }
480
481     void dump(const QMetaProperty &prop)
482     {
483         qml->writeStartObject("Property");
484
485         qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name())));
486 #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 4))
487         if (int revision = prop.revision())
488             qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
489 #endif
490         writeTypeProperties(prop.typeName(), prop.isWritable());
491
492         qml->writeEndObject();
493     }
494
495     void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals)
496     {
497         if (meth.methodType() == QMetaMethod::Signal) {
498             if (meth.access() != QMetaMethod::Protected)
499                 return; // nothing to do.
500         } else if (meth.access() != QMetaMethod::Public) {
501             return; // nothing to do.
502         }
503
504         QByteArray name = meth.signature();
505         int lparenIndex = name.indexOf('(');
506         if (lparenIndex == -1) {
507             return; // invalid signature
508         }
509         name = name.left(lparenIndex);
510         const QString typeName = convertToId(meth.typeName());
511
512         if (implicitSignals.contains(name)
513                 && !meth.revision()
514                 && meth.methodType() == QMetaMethod::Signal
515                 && meth.parameterNames().isEmpty()
516                 && typeName.isEmpty()) {
517             // don't mention implicit signals
518             return;
519         }
520
521         if (meth.methodType() == QMetaMethod::Signal)
522             qml->writeStartObject(QLatin1String("Signal"));
523         else
524             qml->writeStartObject(QLatin1String("Method"));
525
526         qml->writeScriptBinding(QLatin1String("name"), enquote(name));
527
528 #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 4))
529         if (int revision = meth.revision())
530             qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
531 #endif
532
533         if (! typeName.isEmpty())
534             qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
535
536         for (int i = 0; i < meth.parameterTypes().size(); ++i) {
537             QByteArray argName = meth.parameterNames().at(i);
538
539             qml->writeStartObject(QLatin1String("Parameter"));
540             if (! argName.isEmpty())
541                 qml->writeScriptBinding(QLatin1String("name"), enquote(argName));
542             writeTypeProperties(meth.parameterTypes().at(i), true);
543             qml->writeEndObject();
544         }
545
546         qml->writeEndObject();
547     }
548
549     void dump(const QMetaEnum &e)
550     {
551         qml->writeStartObject(QLatin1String("Enum"));
552         qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name())));
553
554         QList<QPair<QString, QString> > namesValues;
555         for (int index = 0; index < e.keyCount(); ++index) {
556             namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index))));
557         }
558
559         qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues);
560         qml->writeEndObject();
561     }
562 };
563
564
565 enum ExitCode {
566     EXIT_INVALIDARGUMENTS = 1,
567     EXIT_SEGV = 2,
568     EXIT_IMPORTERROR = 3
569 };
570
571 #ifdef Q_OS_UNIX
572 void sigSegvHandler(int) {
573     fprintf(stderr, "Error: SEGV\n");
574     if (!currentProperty.isEmpty())
575         fprintf(stderr, "While processing the property '%s', which probably has uninitialized data.\n", currentProperty.toLatin1().constData());
576     if (!inObjectInstantiation.isEmpty())
577         fprintf(stderr, "While instantiating the object '%s'.\n", inObjectInstantiation.toLatin1().constData());
578     exit(EXIT_SEGV);
579 }
580 #endif
581
582 void printUsage(const QString &appName)
583 {
584     qWarning() << qPrintable(QString(
585                                  "Usage: %1 [-v] [-notrelocatable] module.uri version [module/import/path]\n"
586                                  "       %1 [-v] -path path/to/qmldir/directory [version]\n"
587                                  "       %1 [-v] -builtins\n"
588                                  "Example: %1 Qt.labs.particles 4.7 /home/user/dev/qt-install/imports").arg(
589                                  appName));
590 }
591
592 int main(int argc, char *argv[])
593 {
594 #ifdef Q_OS_UNIX
595     // qmldump may crash, but we don't want any crash handlers to pop up
596     // therefore we intercept the segfault and just exit() ourselves
597     struct sigaction sigAction;
598
599     sigemptyset(&sigAction.sa_mask);
600     sigAction.sa_handler = &sigSegvHandler;
601     sigAction.sa_flags   = 0;
602
603     sigaction(SIGSEGV, &sigAction, 0);
604 #endif
605
606 #ifdef QT_SIMULATOR
607     // Running this application would bring up the Qt Simulator (since it links QtGui), avoid that!
608     QtSimulatorPrivate::SimulatorConnection::createStubInstance();
609 #endif
610     QApplication app(argc, argv);
611     const QStringList args = app.arguments();
612     const QString appName = QFileInfo(app.applicationFilePath()).baseName();
613     if (args.size() < 2) {
614         printUsage(appName);
615         return EXIT_INVALIDARGUMENTS;
616     }
617
618     QString pluginImportUri;
619     QString pluginImportVersion;
620     bool relocatable = true;
621     enum Action { Uri, Path, Builtins };
622     Action action = Uri;
623     {
624         QStringList positionalArgs;
625         foreach (const QString &arg, args) {
626             if (!arg.startsWith(QLatin1Char('-'))) {
627                 positionalArgs.append(arg);
628                 continue;
629             }
630
631             if (arg == QLatin1String("--notrelocatable")
632                     || arg == QLatin1String("-notrelocatable")) {
633                 relocatable = false;
634             } else if (arg == QLatin1String("--path")
635                        || arg == QLatin1String("-path")) {
636                 action = Path;
637             } else if (arg == QLatin1String("--builtins")
638                        || arg == QLatin1String("-builtins")) {
639                 action = Builtins;
640             } else if (arg == QLatin1String("-v")) {
641                 verbose = true;
642             } else {
643                 qWarning() << "Invalid argument: " << arg;
644                 return EXIT_INVALIDARGUMENTS;
645             }
646         }
647
648         if (action == Uri) {
649             if (positionalArgs.size() != 3 && positionalArgs.size() != 4) {
650                 qWarning() << "Incorrect number of positional arguments";
651                 return EXIT_INVALIDARGUMENTS;
652             }
653             pluginImportUri = positionalArgs[1];
654             pluginImportVersion = positionalArgs[2];
655             if (positionalArgs.size() >= 4)
656                 pluginImportPath = positionalArgs[3];
657         } else if (action == Path) {
658             if (positionalArgs.size() != 2 && positionalArgs.size() != 3) {
659                 qWarning() << "Incorrect number of positional arguments";
660                 return EXIT_INVALIDARGUMENTS;
661             }
662             pluginImportPath = QDir::fromNativeSeparators(positionalArgs[1]);
663             if (positionalArgs.size() == 3)
664                 pluginImportVersion = positionalArgs[2];
665         } else if (action == Builtins) {
666             if (positionalArgs.size() != 1) {
667                 qWarning() << "Incorrect number of positional arguments";
668                 return EXIT_INVALIDARGUMENTS;
669             }
670         }
671     }
672
673     QDeclarativeEngine engine;
674     if (!pluginImportPath.isEmpty()) {
675         QDir cur = QDir::current();
676         cur.cd(pluginImportPath);
677         pluginImportPath = cur.absolutePath();
678         QDir::setCurrent(pluginImportPath);
679         engine.addImportPath(pluginImportPath);
680     }
681
682     // load the QtQuick 1 & 2 plugins
683     {
684         QByteArray code("import QtQuick 1.0 as Q1\nimport QtQuick 2.0 as Q2\nQ2.QtObject {}");
685         QDeclarativeComponent c(&engine);
686         c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loadqtquick.qml"));
687         c.create();
688         if (!c.errors().isEmpty()) {
689             foreach (const QDeclarativeError &error, c.errors())
690                 qWarning() << error.toString();
691             return EXIT_IMPORTERROR;
692         }
693     }
694
695     // find all QMetaObjects reachable from the builtin module
696     QSet<const QMetaObject *> defaultReachable = collectReachableMetaObjects(&engine);
697     QList<QDeclarativeType *> defaultTypes = QDeclarativeMetaType::qmlTypes();
698
699     // add some otherwise unreachable QMetaObjects
700     defaultReachable.insert(&QQuickMouseEvent::staticMetaObject);
701     // QQuickKeyEvent, QQuickPinchEvent, QQuickDropEvent are not exported
702
703     // this will hold the meta objects we want to dump information of
704     QSet<const QMetaObject *> metas;
705
706     if (action == Builtins) {
707         metas = defaultReachable;
708     } else {
709         // find a valid QtQuick import
710         QByteArray importCode;
711         QDeclarativeType *qtObjectType = QDeclarativeMetaType::qmlType(&QObject::staticMetaObject);
712         if (!qtObjectType) {
713             qWarning() << "Could not find QtObject type";
714             importCode = QByteArray("import QtQuick 2.0\n");
715         } else {
716             QString module = qtObjectType->qmlTypeName();
717             module = module.mid(0, module.lastIndexOf(QLatin1Char('/')));
718             importCode = QString("import %1 %2.%3\n").arg(module,
719                                                           QString::number(qtObjectType->majorVersion()),
720                                                           QString::number(qtObjectType->minorVersion())).toUtf8();
721         }
722
723         // find all QMetaObjects reachable when the specified module is imported
724         if (action != Path) {
725             importCode += QString("import %0 %1\n").arg(pluginImportUri, pluginImportVersion).toAscii();
726         } else {
727             // pluginImportVersion can be empty
728             importCode += QString("import \".\" %2\n").arg(pluginImportVersion).toAscii();
729         }
730
731         // create a component with these imports to make sure the imports are valid
732         // and to populate the declarative meta type system
733         {
734             QByteArray code = importCode;
735             code += "QtObject {}";
736             QDeclarativeComponent c(&engine);
737
738             c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml"));
739             c.create();
740             if (!c.errors().isEmpty()) {
741                 foreach (const QDeclarativeError &error, c.errors())
742                     qWarning() << error.toString();
743                 return EXIT_IMPORTERROR;
744             }
745         }
746
747         QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, defaultTypes);
748         candidates.subtract(defaultReachable);
749
750         // Also eliminate meta objects with the same classname.
751         // This is required because extended objects seem not to share
752         // a single meta object instance.
753         QSet<QByteArray> defaultReachableNames;
754         foreach (const QMetaObject *mo, defaultReachable)
755             defaultReachableNames.insert(QByteArray(mo->className()));
756         foreach (const QMetaObject *mo, candidates) {
757             if (!defaultReachableNames.contains(mo->className()))
758                 metas.insert(mo);
759         }
760     }
761
762     // setup static rewrites of type names
763     cppToId.insert("QString", "string");
764     cppToId.insert("QDeclarativeEasingValueType::Type", "Type");
765
766     // start dumping data
767     QByteArray bytes;
768     QmlStreamWriter qml(&bytes);
769
770     qml.writeStartDocument();
771     qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 1);
772     qml.write("\n"
773               "// This file describes the plugin-supplied types contained in the library.\n"
774               "// It is used for QML tooling purposes only.\n"
775               "\n");
776     qml.writeStartObject("Module");
777
778     // put the metaobjects into a map so they are always dumped in the same order
779     QMap<QString, const QMetaObject *> nameToMeta;
780     foreach (const QMetaObject *meta, metas)
781         nameToMeta.insert(convertToId(meta), meta);
782
783     Dumper dumper(&qml);
784     if (relocatable)
785         dumper.setRelocatableModuleUri(pluginImportUri);
786     foreach (const QMetaObject *meta, nameToMeta) {
787         dumper.dump(meta);
788     }
789
790     // define QEasingCurve as an extension of QDeclarativeEasingValueType, this way
791     // properties using the QEasingCurve type get useful type information.
792     if (pluginImportUri.isEmpty())
793         dumper.writeEasingCurve();
794
795     // write out module api elements
796     foreach (const ModuleApi &api, moduleApis) {
797         dumper.dump(api);
798     }
799
800     qml.writeEndObject();
801     qml.writeEndDocument();
802
803     std::cout << bytes.constData() << std::flush;
804
805     // workaround to avoid crashes on exit
806     QTimer timer;
807     timer.setSingleShot(true);
808     timer.setInterval(0);
809     QObject::connect(&timer, SIGNAL(timeout()), &app, SLOT(quit()));
810     timer.start();
811
812     return app.exec();
813 }