1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
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 <QtQml/qqmlengine.h>
30 #include <QtQml/private/qqmlengine_p.h>
31 #include <QtQml/private/qqmlmetatype_p.h>
32 #include <QtQml/private/qqmlopenmetaobject_p.h>
33 #include <QtQuick/private/qquickevents_p_p.h>
34 #include <QtQuick/private/qquickpincharea_p.h>
35
36 #ifdef QT_WIDGETS_LIB
37 #include <QApplication>
38 #endif // QT_WIDGETS_LIB
39
40 #include <QtGui/QGuiApplication>
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QSet>
44 #include <QtCore/QStringList>
45 #include <QtCore/QTimer>
46 #include <QtCore/QMetaObject>
47 #include <QtCore/QMetaProperty>
48 #include <QtCore/QDebug>
49 #include <QtCore/QJsonDocument>
50 #include <QtCore/QJsonParseError>
51 #include <QtCore/QJsonValue>
52 #include <QtCore/QJsonArray>
53 #include <QtCore/QJsonObject>
54 #include <QtCore/QProcess>
55 #include <QtCore/private/qobject_p.h>
56 #include <QtCore/private/qmetaobject_p.h>
57
58 #include <QRegularExpression>
59 #include <iostream>
60 #include <algorithm>
61
62 #include "qmltypereader.h"
63 #include "qmlstreamwriter.h"
64
65 #ifdef QT_SIMULATOR
66 #include <QtGui/private/qsimulatorconnection_p.h>
67 #endif
68
69 #ifdef Q_OS_WIN
70 # if !defined(Q_CC_MINGW)
71 # include <crtdbg.h>
72 # endif
73 #include <qt_windows.h>
74 #endif
75
76 namespace {
77
78 const uint qtQmlMajorVersion = 2;
79 const uint qtQmlMinorVersion = 0;
80 const uint qtQuickMajorVersion = 2;
81 const uint qtQuickMinorVersion = 0;
82
83 const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2")
84 .arg(qtQuickMajorVersion)
85 .arg(qtQuickMinorVersion);
86
87 QString pluginImportPath;
88 bool verbose = false;
89 bool creatable = true;
90
91 QString currentProperty;
92 QString inObjectInstantiation;
93
94 }
95
enquote(const QString & string)96 static QString enquote(const QString &string)
97 {
98 QString s = string;
99 return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\"))
100 .replace(QLatin1Char('"'),QLatin1String("\\\"")));
101 }
102
103 struct QmlVersionInfo
104 {
105 QString pluginImportUri;
106 int majorVersion;
107 int minorVersion;
108 bool strict;
109 };
110
matchingImportUri(const QQmlType & ty,const QmlVersionInfo & versionInfo)111 static bool matchingImportUri(const QQmlType &ty, const QmlVersionInfo& versionInfo) {
112 if (versionInfo.strict) {
113 return (versionInfo.pluginImportUri == ty.module()
114 && (ty.majorVersion() == versionInfo.majorVersion || ty.majorVersion() == -1))
115 || ty.module().isEmpty();
116 }
117 return ty.module().isEmpty()
118 || versionInfo.pluginImportUri == ty.module()
119 || ty.module().startsWith(versionInfo.pluginImportUri + QLatin1Char('.'));
120 }
121
collectReachableMetaObjects(const QMetaObject * meta,QSet<const QMetaObject * > * metas,const QmlVersionInfo & info,bool extended=false,bool alreadyChangedModule=false)122 void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info, bool extended = false, bool alreadyChangedModule = false)
123 {
124 auto ty = QQmlMetaType::qmlType(meta);
125 if (! meta || metas->contains(meta))
126 return;
127
128 if (matchingImportUri(ty, info)) {
129 if (!alreadyChangedModule) {
130 // dynamic meta objects can break things badly
131 // but extended types are usually fine
132 const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data);
133 if (extended || !(mop->flags & DynamicMetaObject))
134 metas->insert(meta);
135 } else if (!ty.module().isEmpty()) { // empty module (e.g. from an attached property) would cause a (false) match; do not warn about them
136 qWarning() << "Circular module dependency cannot be expressed in plugin.qmltypes file"
137 << "Object was:" << meta->className()
138 << ty.module() << info.pluginImportUri;
139 }
140 } else if (!ty.module().isEmpty()) {
141 alreadyChangedModule = true;
142 }
143
144 collectReachableMetaObjects(meta->superClass(), metas, info, /*extended=*/ false, alreadyChangedModule);
145 }
146
collectReachableMetaObjects(QObject * object,QSet<const QMetaObject * > * metas,const QmlVersionInfo & info)147 void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info)
148 {
149 if (! object)
150 return;
151
152 const QMetaObject *meta = object->metaObject();
153 if (verbose)
154 std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl;
155 collectReachableMetaObjects(meta, metas, info);
156
157 for (int index = 0; index < meta->propertyCount(); ++index) {
158 QMetaProperty prop = meta->property(index);
159 if (QQmlMetaType::isQObject(prop.userType())) {
160 if (verbose)
161 std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl;
162 currentProperty = QString("%1::%2").arg(meta->className(), prop.name());
163
164 // if the property was not initialized during construction,
165 // accessing a member of oo is going to cause a segmentation fault
166 QObject *oo = QQmlMetaType::toQObject(prop.read(object));
167 if (oo && !metas->contains(oo->metaObject()))
168 collectReachableMetaObjects(oo, metas, info);
169 currentProperty.clear();
170 }
171 }
172 }
173
collectReachableMetaObjects(QQmlEnginePrivate * engine,const QQmlType & ty,QSet<const QMetaObject * > * metas,const QmlVersionInfo & info)174 void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet<const QMetaObject *> *metas, const QmlVersionInfo& info)
175 {
176 collectReachableMetaObjects(ty.baseMetaObject(), metas, info, ty.isExtendedType());
177 if (ty.attachedPropertiesType(engine) && matchingImportUri(ty, info)) {
178 collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas, info);
179 }
180 }
181
182 /* We want to add the MetaObject for 'Qt' to the list, this is a
183 simple way to access it.
184 */
185 class FriendlyQObject: public QObject
186 {
187 public:
qtMeta()188 static const QMetaObject *qtMeta() { return &staticQtMetaObject; }
189 };
190
191 /* When we dump a QMetaObject, we want to list all the types it is exported as.
192 To do this, we need to find the QQmlTypes associated with this
193 QMetaObject.
194 */
195 static QHash<QByteArray, QSet<QQmlType> > qmlTypesByCppName;
196
197 static QHash<QByteArray, QByteArray> cppToId;
198
199 /* Takes a C++ type name, such as Qt::LayoutDirection or QString and
200 maps it to how it should appear in the description file.
201
202 These names need to be unique globally, so we don't change the C++ symbol's
203 name much. It is mostly used to for explicit translations such as
204 QString->string and translations for extended QML objects.
205 */
convertToId(const QByteArray & cppName)206 QByteArray convertToId(const QByteArray &cppName)
207 {
208 return cppToId.value(cppName, cppName);
209 }
210
convertToId(const QMetaObject * mo)211 QByteArray convertToId(const QMetaObject *mo)
212 {
213 QByteArray className(mo->className());
214 if (!className.isEmpty())
215 return convertToId(className);
216
217 // likely a metaobject generated for an extended qml object
218 if (mo->superClass()) {
219 className = convertToId(mo->superClass());
220 className.append("_extended");
221 return className;
222 }
223
224 static QHash<const QMetaObject *, QByteArray> generatedNames;
225 className = generatedNames.value(mo);
226 if (!className.isEmpty())
227 return className;
228
229 std::cerr << "Found a QMetaObject without a className, generating a random name" << std::endl;
230 className = QByteArray("error-unknown-name-");
231 className.append(QByteArray::number(generatedNames.size()));
232 generatedNames.insert(mo, className);
233 return className;
234 }
235
236
237 // Collect all metaobjects for types registered with qmlRegisterType() without parameters
collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate * engine,QSet<const QMetaObject * > & metas,QMap<QString,QList<QQmlType>> & compositeTypes,const QmlVersionInfo & info)238 void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet<const QMetaObject *>& metas,
239 QMap<QString, QList<QQmlType>> &compositeTypes, const QmlVersionInfo &info) {
240 const auto qmlAllTypes = QQmlMetaType::qmlAllTypes();
241 for (const QQmlType &ty : qmlAllTypes) {
242 if (!metas.contains(ty.baseMetaObject())) {
243 if (!ty.isComposite()) {
244 collectReachableMetaObjects(engine, ty, &metas, info);
245 } else if (matchingImportUri(ty, info)) {
246 compositeTypes[ty.elementName()].append(ty);
247 }
248 }
249 }
250 }
251
collectReachableMetaObjects(QQmlEngine * engine,QSet<const QMetaObject * > & noncreatables,QSet<const QMetaObject * > & singletons,QMap<QString,QList<QQmlType>> & compositeTypes,const QmlVersionInfo & info,const QList<QQmlType> & skip=QList<QQmlType> ())252 QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine,
253 QSet<const QMetaObject *> &noncreatables,
254 QSet<const QMetaObject *> &singletons,
255 QMap<QString, QList<QQmlType>> &compositeTypes,
256 const QmlVersionInfo &info,
257 const QList<QQmlType> &skip = QList<QQmlType>()
258 )
259 {
260 QSet<const QMetaObject *> metas;
261 metas.insert(FriendlyQObject::qtMeta());
262
263 const auto qmlTypes = QQmlMetaType::qmlTypes();
264 for (const QQmlType &ty : qmlTypes) {
265 if (!matchingImportUri(ty,info))
266 continue;
267 if (!ty.isCreatable())
268 noncreatables.insert(ty.baseMetaObject());
269 if (ty.isSingleton())
270 singletons.insert(ty.baseMetaObject());
271 if (!ty.isComposite()) {
272 qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty);
273 collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas, info);
274 } else {
275 compositeTypes[ty.elementName()].append(ty);
276 }
277 }
278
279 if (creatable) {
280 // find even more QMetaObjects by instantiating QML types and running
281 // over the instances
282 for (const QQmlType &ty : qmlTypes) {
283 if (!matchingImportUri(ty, info))
284 continue;
285 if (skip.contains(ty))
286 continue;
287 if (ty.isExtendedType())
288 continue;
289 if (!ty.isCreatable())
290 continue;
291 if (ty.typeName() == "QQmlComponent")
292 continue;
293
294 QString tyName = ty.qmlTypeName();
295 tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1);
296 if (tyName.isEmpty())
297 continue;
298
299 inObjectInstantiation = tyName;
300 QObject *object = nullptr;
301
302 if (ty.isSingleton()) {
303 QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo();
304 if (!siinfo) {
305 std::cerr << "Internal error, " << qPrintable(tyName)
306 << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")"
307 << " is singleton, but has no singletonInstanceInfo" << std::endl;
308 continue;
309 }
310 if (ty.isQObjectSingleton()) {
311 if (verbose)
312 std::cerr << "Trying to get singleton for " << qPrintable(tyName)
313 << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl;
314 collectReachableMetaObjects(object, &metas, info);
315 object = QQmlEnginePrivate::get(engine)->singletonInstance<QObject*>(ty);
316 } else {
317 inObjectInstantiation.clear();
318 continue; // we don't handle QJSValue singleton types.
319 }
320 } else {
321 if (verbose)
322 std::cerr << "Trying to create object " << qPrintable( tyName )
323 << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl;
324 object = ty.create();
325 }
326
327 inObjectInstantiation.clear();
328
329 if (object) {
330 if (verbose)
331 std::cerr << "Got " << qPrintable( tyName )
332 << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl;
333 collectReachableMetaObjects(object, &metas, info);
334 object->deleteLater();
335 } else {
336 std::cerr << "Could not create " << qPrintable(tyName) << std::endl;
337 }
338 }
339 }
340
341 collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes, info);
342
343 return metas;
344 }
345
346 class KnownAttributes {
347 QHash<QByteArray, int> m_properties;
348 QHash<QByteArray, QHash<int, int> > m_methods;
349 public:
knownMethod(const QByteArray & name,int nArgs,int revision)350 bool knownMethod(const QByteArray &name, int nArgs, int revision)
351 {
352 if (m_methods.contains(name)) {
353 QHash<int, int> overloads = m_methods.value(name);
354 if (overloads.contains(nArgs) && overloads.value(nArgs) <= revision)
355 return true;
356 }
357 m_methods[name][nArgs] = revision;
358 return false;
359 }
360
knownProperty(const QByteArray & name,int revision)361 bool knownProperty(const QByteArray &name, int revision)
362 {
363 if (m_properties.contains(name) && m_properties.value(name) <= revision)
364 return true;
365 m_properties[name] = revision;
366 return false;
367 }
368 };
369
370 class Dumper
371 {
372 QmlStreamWriter *qml;
373 QString relocatableModuleUri;
374
375 public:
Dumper(QmlStreamWriter * qml)376 Dumper(QmlStreamWriter *qml) : qml(qml) {}
377
setRelocatableModuleUri(const QString & uri)378 void setRelocatableModuleUri(const QString &uri)
379 {
380 relocatableModuleUri = uri;
381 }
382
getExportString(const QQmlType & type,const QmlVersionInfo & versionInfo)383 QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo)
384 {
385 const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri
386 : type.module();
387 const int majorVersion = type.majorVersion() >= 0 ? type.majorVersion()
388 : versionInfo.majorVersion;
389 const int minorVersion = type.minorVersion() >= 0 ? type.minorVersion()
390 : versionInfo.minorVersion;
391
392 const QString versionedElement = type.elementName()
393 + QString::fromLatin1(" %1.%2").arg(majorVersion).arg(minorVersion);
394
395 return enquote((module == relocatableModuleUri)
396 ? versionedElement
397 : module + QLatin1Char('/') + versionedElement);
398 }
399
writeMetaContent(const QMetaObject * meta,KnownAttributes * knownAttributes=nullptr)400 void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr)
401 {
402 QSet<QString> implicitSignals = dumpMetaProperties(meta, 0, knownAttributes);
403
404 if (meta == &QObject::staticMetaObject) {
405 // for QObject, hide deleteLater() and onDestroyed
406 for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) {
407 QMetaMethod method = meta->method(index);
408 QByteArray signature = method.methodSignature();
409 if (signature == QByteArrayLiteral("destroyed(QObject*)")
410 || signature == QByteArrayLiteral("destroyed()")
411 || signature == QByteArrayLiteral("deleteLater()"))
412 continue;
413 dump(method, implicitSignals, knownAttributes);
414 }
415
416 // and add toString(), destroy() and destroy(int)
417 if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, 0)) {
418 qml->writeStartObject(QLatin1String("Method"));
419 qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString")));
420 qml->writeEndObject();
421 }
422 if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, 0)) {
423 qml->writeStartObject(QLatin1String("Method"));
424 qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
425 qml->writeEndObject();
426 }
427 if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, 0)) {
428 qml->writeStartObject(QLatin1String("Method"));
429 qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
430 qml->writeStartObject(QLatin1String("Parameter"));
431 qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay")));
432 qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int")));
433 qml->writeEndObject();
434 qml->writeEndObject();
435 }
436 } else {
437 for (int index = meta->methodOffset(); index < meta->methodCount(); ++index)
438 dump(meta->method(index), implicitSignals, knownAttributes);
439 }
440 }
441
getPrototypeNameForCompositeType(const QMetaObject * metaObject,QList<const QMetaObject * > * objectsToMerge,const QmlVersionInfo & versionInfo)442 QString getPrototypeNameForCompositeType(
443 const QMetaObject *metaObject, QList<const QMetaObject *> *objectsToMerge,
444 const QmlVersionInfo &versionInfo)
445 {
446 auto ty = QQmlMetaType::qmlType(metaObject);
447 QString prototypeName;
448 if (matchingImportUri(ty, versionInfo)) {
449 // dynamic meta objects can break things badly
450 // but extended types are usually fine
451 const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(metaObject->d.data);
452 if (!(mop->flags & DynamicMetaObject) && objectsToMerge
453 && !objectsToMerge->contains(metaObject))
454 objectsToMerge->append(metaObject);
455 const QMetaObject *superMetaObject = metaObject->superClass();
456 if (!superMetaObject) {
457 prototypeName = "QObject";
458 } else {
459 QQmlType superType = QQmlMetaType::qmlType(superMetaObject);
460 if (superType.isValid() && !superType.isComposite())
461 return convertToId(superMetaObject->className());
462 prototypeName = getPrototypeNameForCompositeType(
463 superMetaObject, objectsToMerge, versionInfo);
464 }
465 } else {
466 prototypeName = convertToId(metaObject->className());
467 }
468 return prototypeName;
469 }
470
dumpComposite(QQmlEngine * engine,const QList<QQmlType> & compositeType,const QmlVersionInfo & versionInfo)471 void dumpComposite(QQmlEngine *engine, const QList<QQmlType> &compositeType, const QmlVersionInfo &versionInfo)
472 {
473 for (const QQmlType &type : compositeType)
474 dumpCompositeItem(engine, type, versionInfo);
475 }
476
dumpCompositeItem(QQmlEngine * engine,const QQmlType & compositeType,const QmlVersionInfo & versionInfo)477 void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, const QmlVersionInfo &versionInfo)
478 {
479 QQmlComponent e(engine, compositeType.sourceUrl());
480 if (!e.isReady()) {
481 std::cerr << "WARNING: skipping module " << compositeType.elementName().toStdString()
482 << std::endl << e.errorString().toStdString() << std::endl;
483 return;
484 }
485
486 QObject *object = e.create();
487
488 if (!object)
489 return;
490
491 qml->writeStartObject("Component");
492
493 const QMetaObject *mainMeta = object->metaObject();
494
495 QList<const QMetaObject *> objectsToMerge;
496 KnownAttributes knownAttributes;
497 // Get C++ base class name for the composite type
498 QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge,
499 versionInfo);
500 qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName));
501
502 QString qmlTyName = compositeType.qmlTypeName();
503 const QString exportString = getExportString(compositeType, versionInfo);
504
505 // TODO: why don't we simply output the compositeType.elementName() here?
506 // That would make more sense, but it would change the format quite a bit.
507 qml->writeScriptBinding(QLatin1String("name"), exportString);
508
509 qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString);
510 qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion()));
511 qml->writeBooleanBinding(QLatin1String("isComposite"), true);
512
513 if (compositeType.isSingleton()) {
514 qml->writeBooleanBinding(QLatin1String("isCreatable"), false);
515 qml->writeBooleanBinding(QLatin1String("isSingleton"), true);
516 }
517
518 for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) {
519 QMetaClassInfo classInfo = mainMeta->classInfo(index);
520 if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
521 qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value())));
522 break;
523 }
524 }
525
526 for (const QMetaObject *meta : qAsConst(objectsToMerge)) {
527 for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index)
528 dump(meta->enumerator(index));
529
530 writeMetaContent(meta, &knownAttributes);
531 }
532
533 qml->writeEndObject();
534 }
535
getDefaultProperty(const QMetaObject * meta)536 QString getDefaultProperty(const QMetaObject *meta)
537 {
538 for (int index = meta->classInfoCount() - 1; index >= 0; --index) {
539 QMetaClassInfo classInfo = meta->classInfo(index);
540 if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
541 return QLatin1String(classInfo.value());
542 }
543 }
544 return QString();
545 }
546
547 struct QmlTypeInfo {
QmlTypeInfoDumper::QmlTypeInfo548 QmlTypeInfo() {}
QmlTypeInfoDumper::QmlTypeInfo549 QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId)
550 : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {}
551 QString exportString;
552 int revision = 0;
553 const QMetaObject *extendedObject = nullptr;
554 QByteArray attachedTypeId;
555 };
556
dump(QQmlEnginePrivate * engine,const QMetaObject * meta,bool isUncreatable,bool isSingleton)557 void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton)
558 {
559 qml->writeStartObject("Component");
560
561 QByteArray id = convertToId(meta);
562 qml->writeScriptBinding(QLatin1String("name"), enquote(id));
563
564 // collect type information
565 QVector<QmlTypeInfo> typeInfo;
566 for (QQmlType type : qmlTypesByCppName.value(meta->className())) {
567 const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr;
568 QByteArray attachedTypeId;
569 if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) {
570 // Can happen when a type is registered that returns itself as attachedPropertiesType()
571 // because there is no creatable type to attach to.
572 if (attachedType != meta)
573 attachedTypeId = convertToId(attachedType);
574 }
575 const QString exportString = getExportString(type, { QString(), -1, -1, false });
576 int metaObjectRevision = type.metaObjectRevision();
577 if (extendedObject) {
578 // emulate custom metaobjectrevision out of import
579 metaObjectRevision = type.majorVersion() * 100 + type.minorVersion();
580 }
581
582 QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId };
583 typeInfo.append(info);
584 }
585
586 // sort to ensure stable output
587 std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) {
588 return i1.revision < i2.revision;
589 });
590
591 // determine default property
592 // TODO: support revisioning of default property
593 QString defaultProperty = getDefaultProperty(meta);
594 if (defaultProperty.isEmpty()) {
595 for (const QmlTypeInfo &iter : typeInfo) {
596 if (iter.extendedObject) {
597 defaultProperty = getDefaultProperty(iter.extendedObject);
598 if (!defaultProperty.isEmpty())
599 break;
600 }
601 }
602 }
603 if (!defaultProperty.isEmpty())
604 qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty));
605
606 if (meta->superClass())
607 qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass())));
608
609 if (!typeInfo.isEmpty()) {
610 QMap<QString, QString> exports; // sort exports
611 for (const QmlTypeInfo &iter : typeInfo)
612 exports.insert(iter.exportString, QString::number(iter.revision));
613
614 QStringList exportStrings = exports.keys();
615 QStringList metaObjectRevisions = exports.values();
616 qml->writeArrayBinding(QLatin1String("exports"), exportStrings);
617
618 if (isUncreatable)
619 qml->writeBooleanBinding(QLatin1String("isCreatable"), false);
620
621 if (isSingleton)
622 qml->writeBooleanBinding(QLatin1String("isSingleton"), true);
623
624 qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions);
625
626 for (const QmlTypeInfo &iter : typeInfo) {
627 if (!iter.attachedTypeId.isEmpty()) {
628 qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId));
629 break;
630 }
631 }
632 }
633
634 for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index)
635 dump(meta->enumerator(index));
636
637 writeMetaContent(meta);
638
639 // dump properties from extended metaobjects last
640 for (auto iter : typeInfo) {
641 if (iter.extendedObject)
642 dumpMetaProperties(iter.extendedObject, iter.revision);
643 }
644
645 qml->writeEndObject();
646 }
647
writeEasingCurve()648 void writeEasingCurve()
649 {
650 qml->writeStartObject(QLatin1String("Component"));
651 qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve")));
652 qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType")));
653 qml->writeEndObject();
654 }
655
656 private:
657
658 /* Removes pointer and list annotations from a type name, returning
659 what was removed in isList and isPointer
660 */
removePointerAndList(QByteArray * typeName,bool * isList,bool * isPointer)661 static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer)
662 {
663 static QByteArray declListPrefix = "QQmlListProperty<";
664
665 if (typeName->endsWith('*')) {
666 *isPointer = true;
667 typeName->truncate(typeName->length() - 1);
668 removePointerAndList(typeName, isList, isPointer);
669 } else if (typeName->startsWith(declListPrefix)) {
670 *isList = true;
671 typeName->truncate(typeName->length() - 1); // get rid of the suffix '>'
672 *typeName = typeName->mid(declListPrefix.size());
673 removePointerAndList(typeName, isList, isPointer);
674 }
675
676 *typeName = convertToId(*typeName);
677 }
678
writeTypeProperties(QByteArray typeName,bool isWritable)679 void writeTypeProperties(QByteArray typeName, bool isWritable)
680 {
681 bool isList = false, isPointer = false;
682 removePointerAndList(&typeName, &isList, &isPointer);
683
684 qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
685 if (isList)
686 qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true"));
687 if (!isWritable)
688 qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true"));
689 if (isPointer)
690 qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true"));
691 }
692
dump(const QMetaProperty & prop,int metaRevision=-1,KnownAttributes * knownAttributes=nullptr)693 void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr)
694 {
695 int revision = metaRevision ? metaRevision : prop.revision();
696 QByteArray propName = prop.name();
697 if (knownAttributes && knownAttributes->knownProperty(propName, revision))
698 return;
699 qml->writeStartObject("Property");
700 qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name())));
701 if (revision)
702 qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
703 writeTypeProperties(prop.typeName(), prop.isWritable());
704
705 qml->writeEndObject();
706 }
707
dumpMetaProperties(const QMetaObject * meta,int metaRevision=-1,KnownAttributes * knownAttributes=nullptr)708 QSet<QString> dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr)
709 {
710 QSet<QString> implicitSignals;
711 for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) {
712 const QMetaProperty &property = meta->property(index);
713 dump(property, metaRevision, knownAttributes);
714 if (knownAttributes)
715 knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"),
716 0, property.revision());
717 implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name())));
718 }
719 return implicitSignals;
720 }
721
dump(const QMetaMethod & meth,const QSet<QString> & implicitSignals,KnownAttributes * knownAttributes=nullptr)722 void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals,
723 KnownAttributes *knownAttributes = nullptr)
724 {
725 if (meth.methodType() == QMetaMethod::Signal) {
726 if (meth.access() != QMetaMethod::Public)
727 return; // nothing to do.
728 } else if (meth.access() != QMetaMethod::Public) {
729 return; // nothing to do.
730 }
731
732 QByteArray name = meth.name();
733 const QString typeName = convertToId(meth.typeName());
734
735 if (implicitSignals.contains(name)
736 && !meth.revision()
737 && meth.methodType() == QMetaMethod::Signal
738 && meth.parameterNames().isEmpty()
739 && typeName == QLatin1String("void")) {
740 // don't mention implicit signals
741 return;
742 }
743
744 int revision = meth.revision();
745 if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision))
746 return;
747 if (meth.methodType() == QMetaMethod::Signal)
748 qml->writeStartObject(QLatin1String("Signal"));
749 else
750 qml->writeStartObject(QLatin1String("Method"));
751
752 qml->writeScriptBinding(QLatin1String("name"), enquote(name));
753
754 if (revision)
755 qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision));
756
757 if (typeName != QLatin1String("void"))
758 qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
759
760 for (int i = 0; i < meth.parameterTypes().size(); ++i) {
761 QByteArray argName = meth.parameterNames().at(i);
762
763 qml->writeStartObject(QLatin1String("Parameter"));
764 if (! argName.isEmpty())
765 qml->writeScriptBinding(QLatin1String("name"), enquote(argName));
766 writeTypeProperties(meth.parameterTypes().at(i), true);
767 qml->writeEndObject();
768 }
769
770 qml->writeEndObject();
771 }
772
dump(const QMetaEnum & e)773 void dump(const QMetaEnum &e)
774 {
775 qml->writeStartObject(QLatin1String("Enum"));
776 qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name())));
777
778 QList<QPair<QString, QString> > namesValues;
779 const int keyCount = e.keyCount();
780 namesValues.reserve(keyCount);
781 for (int index = 0; index < keyCount; ++index) {
782 namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index))));
783 }
784
785 qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues);
786 qml->writeEndObject();
787 }
788 };
789
790 enum ExitCode {
791 EXIT_INVALIDARGUMENTS = 1,
792 EXIT_SEGV = 2,
793 EXIT_IMPORTERROR = 3
794 };
795
printUsage(const QString & appName)796 void printUsage(const QString &appName)
797 {
798 std::cerr << qPrintable(QString(
799 "Usage: %1 [-v] [-qapp] [-noinstantiate] [-defaultplatform] [-[non]relocatable] [-dependencies <dependencies.json>] [-merge <file-to-merge.qmltypes>] [-output <output-file.qmltypes>] [-noforceqtquick] module.uri version [module/import/path]\n"
800 " %1 [-v] [-qapp] [-noinstantiate] -path path/to/qmldir/directory [version]\n"
801 " %1 [-v] -builtins\n"
802 "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg(
803 appName)) << std::endl;
804 }
805
readDependenciesData(QString dependenciesFile,const QByteArray & fileData,QStringList * dependencies,const QStringList & urisToSkip,bool forceQtQuickDependency=true)806 static bool readDependenciesData(QString dependenciesFile, const QByteArray &fileData,
807 QStringList *dependencies, const QStringList &urisToSkip,
808 bool forceQtQuickDependency = true) {
809 if (verbose) {
810 std::cerr << "parsing "
811 << qPrintable( dependenciesFile ) << " skipping";
812 for (const QString &uriToSkip : urisToSkip)
813 std::cerr << ' ' << qPrintable(uriToSkip);
814 std::cerr << std::endl;
815 }
816 QJsonParseError parseError;
817 parseError.error = QJsonParseError::NoError;
818 QJsonDocument doc = QJsonDocument::fromJson(fileData, &parseError);
819 if (parseError.error != QJsonParseError::NoError) {
820 std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString()
821 << ":" << parseError.errorString().toStdString() << " at " << parseError.offset
822 << std::endl;
823 return false;
824 }
825 if (doc.isArray()) {
826 const QStringList requiredKeys = QStringList() << QStringLiteral("name")
827 << QStringLiteral("type")
828 << QStringLiteral("version");
829 const auto deps = doc.array();
830 for (const QJsonValue &dep : deps) {
831 if (dep.isObject()) {
832 QJsonObject obj = dep.toObject();
833 for (const QString &requiredKey : requiredKeys)
834 if (!obj.contains(requiredKey) || obj.value(requiredKey).isString())
835 continue;
836 if (obj.value(QStringLiteral("type")).toString() != QLatin1String("module"))
837 continue;
838 QString name = obj.value((QStringLiteral("name"))).toString();
839 QString version = obj.value(QStringLiteral("version")).toString();
840 if (name.isEmpty() || urisToSkip.contains(name) || version.isEmpty())
841 continue;
842 if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) {
843 if (verbose)
844 std::cerr << "skipping private dependecy "
845 << qPrintable( name ) << " " << qPrintable(version) << std::endl;
846 continue;
847 }
848 if (verbose)
849 std::cerr << "appending dependency "
850 << qPrintable( name ) << " " << qPrintable(version) << std::endl;
851 dependencies->append(name + QLatin1Char(' ')+version);
852 }
853 }
854 } else {
855 std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString()
856 << ": expected an array" << std::endl;
857 return false;
858 }
859 // Workaround for avoiding conflicting types when no dependency has been found.
860 //
861 // qmlplugindump used to import QtQuick, so all types defined in QtQuick used to be skipped when dumping.
862 // Now that it imports only Qt, it is no longer the case: if no dependency is found all the types defined
863 // in QtQuick will be dumped, causing conflicts.
864 if (forceQtQuickDependency && dependencies->isEmpty())
865 dependencies->push_back(qtQuickQualifiedName);
866 return true;
867 }
868
readDependenciesFile(const QString & dependenciesFile,QStringList * dependencies,const QStringList & urisToSkip)869 static bool readDependenciesFile(const QString &dependenciesFile, QStringList *dependencies,
870 const QStringList &urisToSkip) {
871 if (!QFileInfo::exists(dependenciesFile)) {
872 std::cerr << "non existing dependencies file " << dependenciesFile.toStdString()
873 << std::endl;
874 return false;
875 }
876 QFile f(dependenciesFile);
877 if (!f.open(QFileDevice::ReadOnly)) {
878 std::cerr << "non existing dependencies file " << dependenciesFile.toStdString()
879 << ", " << f.errorString().toStdString() << std::endl;
880 return false;
881 }
882 QByteArray fileData = f.readAll();
883 return readDependenciesData(dependenciesFile, fileData, dependencies, urisToSkip, false);
884 }
885
getDependencies(const QQmlEngine & engine,const QString & pluginImportUri,const QString & pluginImportVersion,QStringList * dependencies,bool forceQtQuickDependency)886 static bool getDependencies(const QQmlEngine &engine, const QString &pluginImportUri,
887 const QString &pluginImportVersion, QStringList *dependencies,
888 bool forceQtQuickDependency)
889 {
890 QString importScannerExe = QLatin1String("qmlimportscanner");
891 QFileInfo selfExe(QCoreApplication::applicationFilePath());
892 if (!selfExe.suffix().isEmpty())
893 importScannerExe += QLatin1String(".") + selfExe.suffix();
894 QString command = selfExe.absoluteDir().filePath(importScannerExe);
895
896 QStringList commandArgs = QStringList()
897 << QLatin1String("-qmlFiles")
898 << QLatin1String("-");
899 QStringList importPathList = engine.importPathList();
900 importPathList.removeOne(QStringLiteral("qrc:/qt-project.org/imports"));
901 for (const QString &path : importPathList)
902 commandArgs << QLatin1String("-importPath") << path;
903
904 QProcess importScanner;
905 importScanner.start(command, commandArgs, QProcess::ReadWrite);
906 if (!importScanner.waitForStarted())
907 return false;
908
909 importScanner.write("import ");
910 importScanner.write(pluginImportUri.toUtf8());
911 importScanner.write(" ");
912 importScanner.write(pluginImportVersion.toUtf8());
913 importScanner.write("\nQtObject{}\n");
914 importScanner.closeWriteChannel();
915
916 if (!importScanner.waitForFinished()) {
917 std::cerr << "failure to start " << qPrintable(command);
918 for (const QString &arg : qAsConst(commandArgs))
919 std::cerr << ' ' << qPrintable(arg);
920 std::cerr << std::endl;
921 return false;
922 }
923 QByteArray depencenciesData = importScanner.readAllStandardOutput();
924 if (!readDependenciesData(QLatin1String("<outputOfQmlimportscanner>"), depencenciesData,
925 dependencies, QStringList(pluginImportUri), forceQtQuickDependency)) {
926 std::cerr << "failed to process output of qmlimportscanner" << std::endl;
927 if (importScanner.exitCode() != 0)
928 std::cerr << importScanner.readAllStandardError().toStdString();
929 return false;
930 }
931
932 QStringList aux;
933 for (const QString &str : qAsConst(*dependencies)) {
934 if (!str.startsWith("Qt.test.qtestroot"))
935 aux += str;
936 }
937 *dependencies = aux;
938
939 return true;
940 }
941
compactDependencies(QStringList * dependencies)942 bool compactDependencies(QStringList *dependencies)
943 {
944 if (dependencies->isEmpty())
945 return false;
946 dependencies->sort();
947 QStringList oldDep = dependencies->constFirst().split(QLatin1Char(' '));
948 Q_ASSERT(oldDep.size() == 2);
949 int oldPos = 0;
950 for (int idep = 1; idep < dependencies->size(); ++idep) {
951 QString depStr = dependencies->at(idep);
952 const QStringList newDep = depStr.split(QLatin1Char(' '));
953 Q_ASSERT(newDep.size() == 2);
954 if (newDep.constFirst() != oldDep.constFirst()) {
955 if (++oldPos != idep)
956 dependencies->replace(oldPos, depStr);
957 oldDep = newDep;
958 } else {
959 const QStringList v1 = oldDep.constLast().split(QLatin1Char('.'));
960 const QStringList v2 = newDep.constLast().split(QLatin1Char('.'));
961 Q_ASSERT(v1.size() == 2);
962 Q_ASSERT(v2.size() == 2);
963 bool ok;
964 int major1 = v1.first().toInt(&ok);
965 Q_ASSERT(ok);
966 int major2 = v2.first().toInt(&ok);
967 Q_ASSERT(ok);
968 if (major1 != major2) {
969 std::cerr << "Found a dependency on " << qPrintable(oldDep.constFirst())
970 << " with two major versions:" << qPrintable(oldDep.constLast())
971 << " and " << qPrintable(newDep.constLast())
972 << " which is unsupported, discarding smaller version" << std::endl;
973 if (major1 < major2)
974 dependencies->replace(oldPos, depStr);
975 } else {
976 int minor1 = v1.last().toInt(&ok);
977 Q_ASSERT(ok);
978 int minor2 = v2.last().toInt(&ok);
979 Q_ASSERT(ok);
980 if (minor1 < minor2)
981 dependencies->replace(oldPos, depStr);
982 }
983 }
984 }
985 if (++oldPos < dependencies->size()) {
986 *dependencies = dependencies->mid(0, oldPos);
987 return true;
988 }
989 return false;
990 }
991
operator <<(std::wostream & str,const QString & s)992 inline std::wostream &operator<<(std::wostream &str, const QString &s)
993 {
994 #ifdef Q_OS_WIN
995 str << reinterpret_cast<const wchar_t *>(s.utf16());
996 #else
997 str << s.toStdWString();
998 #endif
999 return str;
1000 }
1001
printDebugMessage(QtMsgType,const QMessageLogContext &,const QString & msg)1002 void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg)
1003 {
1004 std::wcerr << msg << std::endl;
1005 // In case of QtFatalMsg the calling code will abort() when appropriate.
1006 }
1007
1008 QT_BEGIN_NAMESPACE
operator <(const QQmlType & a,const QQmlType & b)1009 static bool operator<(const QQmlType &a, const QQmlType &b)
1010 {
1011 return a.qmlTypeName() < b.qmlTypeName()
1012 || (a.qmlTypeName() == b.qmlTypeName()
1013 && ((a.majorVersion() < b.majorVersion())
1014 || (a.majorVersion() == b.majorVersion()
1015 && a.minorVersion() < b.minorVersion())));
1016 }
1017 QT_END_NAMESPACE
1018
main(int argc,char * argv[])1019 int main(int argc, char *argv[])
1020 {
1021 #if defined(Q_OS_WIN) && !defined(Q_CC_MINGW)
1022 // we do not want windows popping up if the module loaded triggers an assert
1023 SetErrorMode(SEM_NOGPFAULTERRORBOX);
1024 _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
1025 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
1026 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
1027 #endif // Q_OS_WIN && !Q_CC_MINGW
1028 // The default message handler might not print to console on some systems. Enforce this.
1029 qInstallMessageHandler(printDebugMessage);
1030
1031 #ifdef QT_SIMULATOR
1032 // Running this application would bring up the Qt Simulator (since it links Qt GUI), avoid that!
1033 QtSimulatorPrivate::SimulatorConnection::createStubInstance();
1034 #endif
1035
1036 // don't require a window manager even though we're a QGuiApplication
1037 bool requireWindowManager = false;
1038 for (int index = 1; index < argc; ++index) {
1039 if (QString::fromLocal8Bit(argv[index]) == "--defaultplatform"
1040 || QString::fromLocal8Bit(argv[index]) == "-defaultplatform") {
1041 requireWindowManager = true;
1042 break;
1043 }
1044 }
1045
1046 if (!requireWindowManager && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM"))
1047 qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal"));
1048 else
1049 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
1050
1051 // Check which kind of application should be instantiated.
1052 bool useQApplication = false;
1053 for (int i = 0; i < argc; ++i) {
1054 QString arg = QLatin1String(argv[i]);
1055 if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp"))
1056 useQApplication = true;
1057 }
1058
1059 #ifdef QT_WIDGETS_LIB
1060 QScopedPointer<QCoreApplication> app(useQApplication
1061 ? new QApplication(argc, argv)
1062 : new QGuiApplication(argc, argv));
1063 #else
1064 Q_UNUSED(useQApplication);
1065 QScopedPointer<QCoreApplication> app(new QGuiApplication(argc, argv));
1066 #endif // QT_WIDGETS_LIB
1067
1068 QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
1069 QStringList args = app->arguments();
1070 const QString appName = QFileInfo(app->applicationFilePath()).baseName();
1071 if (args.size() < 2) {
1072 printUsage(appName);
1073 return EXIT_INVALIDARGUMENTS;
1074 }
1075
1076 QString outputFilename;
1077 QString pluginImportUri;
1078 QString pluginImportVersion;
1079 bool relocatable = true;
1080 QString dependenciesFile;
1081 QString mergeFile;
1082 bool forceQtQuickDependency = true;
1083 bool strict = false;
1084 enum Action { Uri, Path, Builtins };
1085 Action action = Uri;
1086 {
1087 QStringList positionalArgs;
1088
1089 for (int iArg = 0; iArg < args.size(); ++iArg) {
1090 const QString &arg = args.at(iArg);
1091 if (!arg.startsWith(QLatin1Char('-'))) {
1092 positionalArgs.append(arg);
1093 continue;
1094 }
1095 if (arg == QLatin1String("--dependencies")
1096 || arg == QLatin1String("-dependencies")) {
1097 if (++iArg == args.size()) {
1098 std::cerr << "missing dependencies file" << std::endl;
1099 return EXIT_INVALIDARGUMENTS;
1100 }
1101 dependenciesFile = args.at(iArg);
1102
1103 // Remove absolute path so that it does not show up in the
1104 // printed command line inside the plugins.qmltypes file.
1105 args[iArg] = QFileInfo(args.at(iArg)).fileName();
1106 } else if (arg == QLatin1String("--merge")
1107 || arg == QLatin1String("-merge")) {
1108 if (++iArg == args.size()) {
1109 std::cerr << "missing merge file" << std::endl;
1110 return EXIT_INVALIDARGUMENTS;
1111 }
1112 mergeFile = args.at(iArg);
1113 } else if (arg == QLatin1String("--notrelocatable")
1114 || arg == QLatin1String("-notrelocatable")
1115 || arg == QLatin1String("--nonrelocatable")
1116 || arg == QLatin1String("-nonrelocatable")) {
1117 relocatable = false;
1118 } else if (arg == QLatin1String("--relocatable")
1119 || arg == QLatin1String("-relocatable")) {
1120 relocatable = true;
1121 } else if (arg == QLatin1String("--noinstantiate")
1122 || arg == QLatin1String("-noinstantiate")) {
1123 creatable = false;
1124 } else if (arg == QLatin1String("--path")
1125 || arg == QLatin1String("-path")) {
1126 action = Path;
1127 } else if (arg == QLatin1String("--builtins")
1128 || arg == QLatin1String("-builtins")) {
1129 action = Builtins;
1130 } else if (arg == QLatin1String("-v")) {
1131 verbose = true;
1132 } else if (arg == QLatin1String("--noforceqtquick")
1133 || arg == QLatin1String("-noforceqtquick")){
1134 forceQtQuickDependency = false;
1135 } else if (arg == QLatin1String("--output")
1136 || arg == QLatin1String("-output")) {
1137 if (++iArg == args.size()) {
1138 std::cerr << "missing output file" << std::endl;
1139 return EXIT_INVALIDARGUMENTS;
1140 }
1141 outputFilename = args.at(iArg);
1142 } else if (arg == QLatin1String("--defaultplatform")
1143 || arg == QLatin1String("-defaultplatform")) {
1144 continue;
1145 } else if (arg == QLatin1String("--qapp")
1146 || arg == QLatin1String("-qapp")) {
1147 continue;
1148 } else if (arg == QLatin1String("--strict")
1149 || arg == QLatin1String("-strict")) {
1150 strict = true;
1151 continue;
1152 } else {
1153 std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl;
1154 return EXIT_INVALIDARGUMENTS;
1155 }
1156 }
1157
1158 if (action == Uri) {
1159 if (positionalArgs.size() != 3 && positionalArgs.size() != 4) {
1160 std::cerr << "Incorrect number of positional arguments" << std::endl;
1161 return EXIT_INVALIDARGUMENTS;
1162 }
1163 pluginImportUri = positionalArgs.at(1);
1164 pluginImportVersion = positionalArgs[2];
1165 if (positionalArgs.size() >= 4)
1166 pluginImportPath = positionalArgs.at(3);
1167 } else if (action == Path) {
1168 if (positionalArgs.size() != 2 && positionalArgs.size() != 3) {
1169 std::cerr << "Incorrect number of positional arguments" << std::endl;
1170 return EXIT_INVALIDARGUMENTS;
1171 }
1172 pluginImportPath = QDir::fromNativeSeparators(positionalArgs.at(1));
1173 if (positionalArgs.size() == 3)
1174 pluginImportVersion = positionalArgs.at(2);
1175 } else if (action == Builtins) {
1176 if (positionalArgs.size() != 1) {
1177 std::cerr << "Incorrect number of positional arguments" << std::endl;
1178 return EXIT_INVALIDARGUMENTS;
1179 }
1180 }
1181 }
1182
1183 QQmlEngine engine;
1184 if (!pluginImportPath.isEmpty()) {
1185 QDir cur = QDir::current();
1186 cur.cd(pluginImportPath);
1187 pluginImportPath = cur.canonicalPath();
1188 QDir::setCurrent(pluginImportPath);
1189 engine.addImportPath(pluginImportPath);
1190 }
1191
1192 // Merge file.
1193 QStringList mergeDependencies;
1194 QString mergeComponents;
1195 if (!mergeFile.isEmpty()) {
1196 const QStringList merge = readQmlTypes(mergeFile);
1197 if (!merge.isEmpty()) {
1198 QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)");
1199 QRegularExpressionMatchIterator i = re.globalMatch(merge[1]);
1200 while (i.hasNext()) {
1201 QRegularExpressionMatch m = i.next();
1202 mergeDependencies << m.captured(1);
1203 }
1204 mergeComponents = merge [2];
1205 }
1206 }
1207
1208 // Dependencies.
1209
1210 bool calculateDependencies = !pluginImportUri.isEmpty() && !pluginImportVersion.isEmpty();
1211 QStringList dependencies;
1212 if (!dependenciesFile.isEmpty())
1213 calculateDependencies = !readDependenciesFile(dependenciesFile, &dependencies,
1214 QStringList(pluginImportUri)) && calculateDependencies;
1215 if (calculateDependencies)
1216 getDependencies(engine, pluginImportUri, pluginImportVersion, &dependencies,
1217 forceQtQuickDependency);
1218
1219 compactDependencies(&dependencies);
1220
1221
1222 QString qtQmlImportString = QString::fromLatin1("import QtQml %1.%2")
1223 .arg(qtQmlMajorVersion)
1224 .arg(qtQmlMinorVersion);
1225
1226 // load the QtQml builtins and the dependencies
1227 {
1228 QByteArray code(qtQmlImportString.toUtf8());
1229 for (const QString &moduleToImport : qAsConst(dependencies)) {
1230 code.append("\nimport ");
1231 code.append(moduleToImport.toUtf8());
1232 }
1233 code.append("\nQtObject {}");
1234 QQmlComponent c(&engine);
1235 c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loaddependencies.qml"));
1236 c.create();
1237 const auto errors = c.errors();
1238 if (!errors.isEmpty()) {
1239 for (const QQmlError &error : errors)
1240 std::cerr << qPrintable( error.toString() ) << std::endl;
1241 return EXIT_IMPORTERROR;
1242 }
1243 }
1244
1245 // find all QMetaObjects reachable from the builtin module
1246 QSet<const QMetaObject *> uncreatableMetas;
1247 QSet<const QMetaObject *> singletonMetas;
1248
1249 // this will hold the meta objects we want to dump information of
1250 QSet<const QMetaObject *> metas;
1251
1252 // composite types we want to dump information of
1253 QMap<QString, QList<QQmlType>> compositeTypes;
1254
1255 int majorVersion = qtQmlMajorVersion, minorVersion = qtQmlMinorVersion;
1256 QmlVersionInfo info;
1257 if (action == Builtins) {
1258 QMap<QString, QList<QQmlType>> defaultCompositeTypes;
1259 QSet<const QMetaObject *> builtins = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes, {QLatin1String("Qt"), majorVersion, minorVersion, strict});
1260 Q_ASSERT(builtins.size() == 1);
1261 metas.insert(*builtins.begin());
1262 } else {
1263 auto versionSplitted = pluginImportVersion.split(".");
1264 bool ok = versionSplitted.size() == 2;
1265 if (!ok)
1266 qCritical("Invalid version number");
1267 else {
1268 majorVersion = versionSplitted.at(0).toInt(&ok);
1269 if (!ok)
1270 qCritical("Invalid major version");
1271 minorVersion = versionSplitted.at(1).toInt(&ok);
1272 if (!ok)
1273 qCritical("Invalid minor version");
1274 }
1275 QList<QQmlType> defaultTypes = QQmlMetaType::qmlTypes();
1276 // find a valid QtQuick import
1277 QByteArray importCode;
1278 QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject);
1279 if (!qtObjectType.isValid()) {
1280 std::cerr << "Could not find QtObject type" << std::endl;
1281 importCode = qtQmlImportString.toUtf8();
1282 } else {
1283 QString module = qtObjectType.qmlTypeName();
1284 module = module.mid(0, module.lastIndexOf(QLatin1Char('/')));
1285 importCode = QString("import %1 %2.%3").arg(module,
1286 QString::number(qtObjectType.majorVersion()),
1287 QString::number(qtObjectType.minorVersion())).toUtf8();
1288 }
1289 // avoid importing dependencies?
1290 for (const QString &moduleToImport : qAsConst(dependencies)) {
1291 importCode.append("\nimport ");
1292 importCode.append(moduleToImport.toUtf8());
1293 }
1294
1295 // find all QMetaObjects reachable when the specified module is imported
1296 if (action != Path) {
1297 importCode += QString("\nimport %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1();
1298 } else {
1299 // pluginImportVersion can be empty
1300 importCode += QString("\nimport \".\" %2\n").arg(pluginImportVersion).toLatin1();
1301 }
1302
1303 // create a component with these imports to make sure the imports are valid
1304 // and to populate the declarative meta type system
1305 {
1306 QByteArray code = importCode;
1307 code += "\nQtObject {}";
1308 QQmlComponent c(&engine);
1309
1310 c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml"));
1311 c.create();
1312 const auto errors = c.errors();
1313 if (!errors.isEmpty()) {
1314 for (const QQmlError &error : errors)
1315 std::cerr << qPrintable( error.toString() ) << std::endl;
1316 return EXIT_IMPORTERROR;
1317 }
1318 }
1319 info = {pluginImportUri, majorVersion, minorVersion, strict};
1320 QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, info, defaultTypes);
1321
1322 for (auto it = compositeTypes.begin(), end = compositeTypes.end(); it != end; ++it) {
1323 std::sort(it->begin(), it->end());
1324 it->erase(std::unique(it->begin(), it->end()), it->end());
1325 }
1326
1327 for (const QMetaObject *mo : qAsConst(candidates)) {
1328 if (mo->className() != QLatin1String("Qt"))
1329 metas.insert(mo);
1330 }
1331 }
1332
1333 // setup static rewrites of type names
1334 cppToId.insert("QString", "string");
1335 cppToId.insert("QQmlEasingValueType::Type", "Type");
1336
1337 // start dumping data
1338 QByteArray bytes;
1339 QmlStreamWriter qml(&bytes);
1340
1341 qml.writeStartDocument();
1342 qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2);
1343 qml.write(QString("\n"
1344 "// This file describes the plugin-supplied types contained in the library.\n"
1345 "// It is used for QML tooling purposes only.\n"
1346 "//\n"
1347 "// This file was auto-generated by:\n"
1348 "// '%1 %2'\n"
1349 "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' '))));
1350 qml.writeStartObject("Module");
1351
1352 // Insert merge dependencies.
1353 if (!mergeDependencies.isEmpty()) {
1354 dependencies << mergeDependencies;
1355 }
1356 compactDependencies(&dependencies);
1357
1358 QStringList quotedDependencies;
1359 for (const QString &dep : qAsConst(dependencies))
1360 quotedDependencies << enquote(dep);
1361 qml.writeArrayBinding("dependencies", quotedDependencies);
1362
1363 // put the metaobjects into a map so they are always dumped in the same order
1364 QMap<QString, const QMetaObject *> nameToMeta;
1365 for (const QMetaObject *meta : qAsConst(metas))
1366 nameToMeta.insert(convertToId(meta), meta);
1367
1368 Dumper dumper(&qml);
1369 if (relocatable)
1370 dumper.setRelocatableModuleUri(pluginImportUri);
1371 for (const QMetaObject *meta : qAsConst(nameToMeta)) {
1372 dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta));
1373 }
1374
1375 QMap<QString, QList<QQmlType>>::const_iterator iter = compositeTypes.constBegin();
1376 for (; iter != compositeTypes.constEnd(); ++iter)
1377 dumper.dumpComposite(&engine, iter.value(), info);
1378
1379 // define QEasingCurve as an extension of QQmlEasingValueType, this way
1380 // properties using the QEasingCurve type get useful type information.
1381 if (pluginImportUri.isEmpty())
1382 dumper.writeEasingCurve();
1383
1384 // Insert merge file.
1385 qml.write(mergeComponents);
1386
1387 qml.writeEndObject();
1388 qml.writeEndDocument();
1389
1390 if (!outputFilename.isEmpty()) {
1391 QFile file(outputFilename);
1392 if (file.open(QIODevice::WriteOnly)) {
1393 QTextStream stream(&file);
1394 stream << bytes.constData();
1395 }
1396 } else {
1397 std::cout << bytes.constData() << std::flush;
1398 }
1399
1400 // workaround to avoid crashes on exit
1401 QTimer timer;
1402 timer.setSingleShot(true);
1403 timer.setInterval(0);
1404 QObject::connect(&timer, SIGNAL(timeout()), app.data(), SLOT(quit()));
1405 timer.start();
1406
1407 return app->exec();
1408 }
1409