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