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