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