| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtQml/qqmlengine.h> |
| #include <QtQml/private/qqmlengine_p.h> |
| #include <QtQml/private/qqmlmetatype_p.h> |
| #include <QtQml/private/qqmlopenmetaobject_p.h> |
| #include <QtQuick/private/qquickevents_p_p.h> |
| #include <QtQuick/private/qquickpincharea_p.h> |
| |
| #ifdef QT_WIDGETS_LIB |
| #include <QApplication> |
| #endif // QT_WIDGETS_LIB |
| |
| #include <QtGui/QGuiApplication> |
| #include <QtCore/QDir> |
| #include <QtCore/QFileInfo> |
| #include <QtCore/QSet> |
| #include <QtCore/QStringList> |
| #include <QtCore/QTimer> |
| #include <QtCore/QMetaObject> |
| #include <QtCore/QMetaProperty> |
| #include <QtCore/QDebug> |
| #include <QtCore/QJsonDocument> |
| #include <QtCore/QJsonParseError> |
| #include <QtCore/QJsonValue> |
| #include <QtCore/QJsonArray> |
| #include <QtCore/QJsonObject> |
| #include <QtCore/QProcess> |
| #include <QtCore/private/qobject_p.h> |
| #include <QtCore/private/qmetaobject_p.h> |
| |
| #include <QRegularExpression> |
| #include <iostream> |
| #include <algorithm> |
| |
| #include "qmltypereader.h" |
| #include "qmlstreamwriter.h" |
| |
| #ifdef QT_SIMULATOR |
| #include <QtGui/private/qsimulatorconnection_p.h> |
| #endif |
| |
| #ifdef Q_OS_WIN |
| # if !defined(Q_CC_MINGW) |
| # include <crtdbg.h> |
| # endif |
| #include <qt_windows.h> |
| #endif |
| |
| namespace { |
| |
| const uint qtQmlMajorVersion = 2; |
| const uint qtQmlMinorVersion = 0; |
| const uint qtQuickMajorVersion = 2; |
| const uint qtQuickMinorVersion = 0; |
| |
| const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2") |
| .arg(qtQuickMajorVersion) |
| .arg(qtQuickMinorVersion); |
| |
| QString pluginImportPath; |
| bool verbose = false; |
| bool creatable = true; |
| |
| QString currentProperty; |
| QString inObjectInstantiation; |
| |
| } |
| |
| static QString enquote(const QString &string) |
| { |
| QString s = string; |
| return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) |
| .replace(QLatin1Char('"'),QLatin1String("\\\""))); |
| } |
| |
| struct QmlVersionInfo |
| { |
| QString pluginImportUri; |
| int majorVersion; |
| int minorVersion; |
| bool strict; |
| }; |
| |
| static bool matchingImportUri(const QQmlType &ty, const QmlVersionInfo& versionInfo) { |
| if (versionInfo.strict) { |
| return (versionInfo.pluginImportUri == ty.module() |
| && (ty.majorVersion() == versionInfo.majorVersion || ty.majorVersion() == -1)) |
| || ty.module().isEmpty(); |
| } |
| return ty.module().isEmpty() |
| || versionInfo.pluginImportUri == ty.module() |
| || ty.module().startsWith(versionInfo.pluginImportUri + QLatin1Char('.')); |
| } |
| |
| void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info, bool extended = false, bool alreadyChangedModule = false) |
| { |
| auto ty = QQmlMetaType::qmlType(meta); |
| if (! meta || metas->contains(meta)) |
| return; |
| |
| if (matchingImportUri(ty, info)) { |
| if (!alreadyChangedModule) { |
| // dynamic meta objects can break things badly |
| // but extended types are usually fine |
| const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data); |
| if (extended || !(mop->flags & DynamicMetaObject)) |
| metas->insert(meta); |
| } else if (!ty.module().isEmpty()) { // empty module (e.g. from an attached property) would cause a (false) match; do not warn about them |
| qWarning() << "Circular module dependency cannot be expressed in plugin.qmltypes file" |
| << "Object was:" << meta->className() |
| << ty.module() << info.pluginImportUri; |
| } |
| } else if (!ty.module().isEmpty()) { |
| alreadyChangedModule = true; |
| } |
| |
| collectReachableMetaObjects(meta->superClass(), metas, info, /*extended=*/ false, alreadyChangedModule); |
| } |
| |
| void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info) |
| { |
| if (! object) |
| return; |
| |
| const QMetaObject *meta = object->metaObject(); |
| if (verbose) |
| std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; |
| collectReachableMetaObjects(meta, metas, info); |
| |
| for (int index = 0; index < meta->propertyCount(); ++index) { |
| QMetaProperty prop = meta->property(index); |
| if (QQmlMetaType::isQObject(prop.userType())) { |
| if (verbose) |
| std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl; |
| currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); |
| |
| // if the property was not initialized during construction, |
| // accessing a member of oo is going to cause a segmentation fault |
| QObject *oo = QQmlMetaType::toQObject(prop.read(object)); |
| if (oo && !metas->contains(oo->metaObject())) |
| collectReachableMetaObjects(oo, metas, info); |
| currentProperty.clear(); |
| } |
| } |
| } |
| |
| void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet<const QMetaObject *> *metas, const QmlVersionInfo& info) |
| { |
| collectReachableMetaObjects(ty.baseMetaObject(), metas, info, ty.isExtendedType()); |
| if (ty.attachedPropertiesType(engine) && matchingImportUri(ty, info)) { |
| collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas, info); |
| } |
| } |
| |
| /* We want to add the MetaObject for 'Qt' to the list, this is a |
| simple way to access it. |
| */ |
| class FriendlyQObject: public QObject |
| { |
| public: |
| static const QMetaObject *qtMeta() { return &staticQtMetaObject; } |
| }; |
| |
| /* When we dump a QMetaObject, we want to list all the types it is exported as. |
| To do this, we need to find the QQmlTypes associated with this |
| QMetaObject. |
| */ |
| static QHash<QByteArray, QSet<QQmlType> > qmlTypesByCppName; |
| |
| static QHash<QByteArray, QByteArray> cppToId; |
| |
| /* Takes a C++ type name, such as Qt::LayoutDirection or QString and |
| maps it to how it should appear in the description file. |
| |
| These names need to be unique globally, so we don't change the C++ symbol's |
| name much. It is mostly used to for explicit translations such as |
| QString->string and translations for extended QML objects. |
| */ |
| QByteArray convertToId(const QByteArray &cppName) |
| { |
| return cppToId.value(cppName, cppName); |
| } |
| |
| QByteArray convertToId(const QMetaObject *mo) |
| { |
| QByteArray className(mo->className()); |
| if (!className.isEmpty()) |
| return convertToId(className); |
| |
| // likely a metaobject generated for an extended qml object |
| if (mo->superClass()) { |
| className = convertToId(mo->superClass()); |
| className.append("_extended"); |
| return className; |
| } |
| |
| static QHash<const QMetaObject *, QByteArray> generatedNames; |
| className = generatedNames.value(mo); |
| if (!className.isEmpty()) |
| return className; |
| |
| std::cerr << "Found a QMetaObject without a className, generating a random name" << std::endl; |
| className = QByteArray("error-unknown-name-"); |
| className.append(QByteArray::number(generatedNames.size())); |
| generatedNames.insert(mo, className); |
| return className; |
| } |
| |
| |
| // Collect all metaobjects for types registered with qmlRegisterType() without parameters |
| void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet<const QMetaObject *>& metas, |
| QMap<QString, QList<QQmlType>> &compositeTypes, const QmlVersionInfo &info) { |
| const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); |
| for (const QQmlType &ty : qmlAllTypes) { |
| if (!metas.contains(ty.baseMetaObject())) { |
| if (!ty.isComposite()) { |
| collectReachableMetaObjects(engine, ty, &metas, info); |
| } else if (matchingImportUri(ty, info)) { |
| compositeTypes[ty.elementName()].append(ty); |
| } |
| } |
| } |
| } |
| |
| QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, |
| QSet<const QMetaObject *> &noncreatables, |
| QSet<const QMetaObject *> &singletons, |
| QMap<QString, QList<QQmlType>> &compositeTypes, |
| const QmlVersionInfo &info, |
| const QList<QQmlType> &skip = QList<QQmlType>() |
| ) |
| { |
| QSet<const QMetaObject *> metas; |
| metas.insert(FriendlyQObject::qtMeta()); |
| |
| const auto qmlTypes = QQmlMetaType::qmlTypes(); |
| for (const QQmlType &ty : qmlTypes) { |
| if (!matchingImportUri(ty,info)) |
| continue; |
| if (!ty.isCreatable()) |
| noncreatables.insert(ty.baseMetaObject()); |
| if (ty.isSingleton()) |
| singletons.insert(ty.baseMetaObject()); |
| if (!ty.isComposite()) { |
| qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); |
| collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas, info); |
| } else { |
| compositeTypes[ty.elementName()].append(ty); |
| } |
| } |
| |
| if (creatable) { |
| // find even more QMetaObjects by instantiating QML types and running |
| // over the instances |
| for (const QQmlType &ty : qmlTypes) { |
| if (!matchingImportUri(ty, info)) |
| continue; |
| if (skip.contains(ty)) |
| continue; |
| if (ty.isExtendedType()) |
| continue; |
| if (!ty.isCreatable()) |
| continue; |
| if (ty.typeName() == "QQmlComponent") |
| continue; |
| |
| QString tyName = ty.qmlTypeName(); |
| tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1); |
| if (tyName.isEmpty()) |
| continue; |
| |
| inObjectInstantiation = tyName; |
| QObject *object = nullptr; |
| |
| if (ty.isSingleton()) { |
| QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); |
| if (!siinfo) { |
| std::cerr << "Internal error, " << qPrintable(tyName) |
| << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" |
| << " is singleton, but has no singletonInstanceInfo" << std::endl; |
| continue; |
| } |
| if (ty.isQObjectSingleton()) { |
| if (verbose) |
| std::cerr << "Trying to get singleton for " << qPrintable(tyName) |
| << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; |
| collectReachableMetaObjects(object, &metas, info); |
| object = QQmlEnginePrivate::get(engine)->singletonInstance<QObject*>(ty); |
| } else { |
| inObjectInstantiation.clear(); |
| continue; // we don't handle QJSValue singleton types. |
| } |
| } else { |
| if (verbose) |
| std::cerr << "Trying to create object " << qPrintable( tyName ) |
| << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; |
| object = ty.create(); |
| } |
| |
| inObjectInstantiation.clear(); |
| |
| if (object) { |
| if (verbose) |
| std::cerr << "Got " << qPrintable( tyName ) |
| << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; |
| collectReachableMetaObjects(object, &metas, info); |
| object->deleteLater(); |
| } else { |
| std::cerr << "Could not create " << qPrintable(tyName) << std::endl; |
| } |
| } |
| } |
| |
| collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes, info); |
| |
| return metas; |
| } |
| |
| class KnownAttributes { |
| QHash<QByteArray, int> m_properties; |
| QHash<QByteArray, QHash<int, int> > m_methods; |
| public: |
| bool knownMethod(const QByteArray &name, int nArgs, int revision) |
| { |
| if (m_methods.contains(name)) { |
| QHash<int, int> overloads = m_methods.value(name); |
| if (overloads.contains(nArgs) && overloads.value(nArgs) <= revision) |
| return true; |
| } |
| m_methods[name][nArgs] = revision; |
| return false; |
| } |
| |
| bool knownProperty(const QByteArray &name, int revision) |
| { |
| if (m_properties.contains(name) && m_properties.value(name) <= revision) |
| return true; |
| m_properties[name] = revision; |
| return false; |
| } |
| }; |
| |
| class Dumper |
| { |
| QmlStreamWriter *qml; |
| QString relocatableModuleUri; |
| |
| public: |
| Dumper(QmlStreamWriter *qml) : qml(qml) {} |
| |
| void setRelocatableModuleUri(const QString &uri) |
| { |
| relocatableModuleUri = uri; |
| } |
| |
| QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo) |
| { |
| const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri |
| : type.module(); |
| const int majorVersion = type.majorVersion() >= 0 ? type.majorVersion() |
| : versionInfo.majorVersion; |
| const int minorVersion = type.minorVersion() >= 0 ? type.minorVersion() |
| : versionInfo.minorVersion; |
| |
| const QString versionedElement = type.elementName() |
| + QString::fromLatin1(" %1.%2").arg(majorVersion).arg(minorVersion); |
| |
| return enquote((module == relocatableModuleUri) |
| ? versionedElement |
| : module + QLatin1Char('/') + versionedElement); |
| } |
| |
| void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) |
| { |
| QSet<QString> implicitSignals = dumpMetaProperties(meta, 0, knownAttributes); |
| |
| if (meta == &QObject::staticMetaObject) { |
| // for QObject, hide deleteLater() and onDestroyed |
| for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) { |
| QMetaMethod method = meta->method(index); |
| QByteArray signature = method.methodSignature(); |
| if (signature == QByteArrayLiteral("destroyed(QObject*)") |
| || signature == QByteArrayLiteral("destroyed()") |
| || signature == QByteArrayLiteral("deleteLater()")) |
| continue; |
| dump(method, implicitSignals, knownAttributes); |
| } |
| |
| // and add toString(), destroy() and destroy(int) |
| if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, 0)) { |
| qml->writeStartObject(QLatin1String("Method")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString"))); |
| qml->writeEndObject(); |
| } |
| if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, 0)) { |
| qml->writeStartObject(QLatin1String("Method")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); |
| qml->writeEndObject(); |
| } |
| if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, 0)) { |
| qml->writeStartObject(QLatin1String("Method")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); |
| qml->writeStartObject(QLatin1String("Parameter")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay"))); |
| qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int"))); |
| qml->writeEndObject(); |
| qml->writeEndObject(); |
| } |
| } else { |
| for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) |
| dump(meta->method(index), implicitSignals, knownAttributes); |
| } |
| } |
| |
| QString getPrototypeNameForCompositeType( |
| const QMetaObject *metaObject, QList<const QMetaObject *> *objectsToMerge, |
| const QmlVersionInfo &versionInfo) |
| { |
| auto ty = QQmlMetaType::qmlType(metaObject); |
| QString prototypeName; |
| if (matchingImportUri(ty, versionInfo)) { |
| // dynamic meta objects can break things badly |
| // but extended types are usually fine |
| const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(metaObject->d.data); |
| if (!(mop->flags & DynamicMetaObject) && objectsToMerge |
| && !objectsToMerge->contains(metaObject)) |
| objectsToMerge->append(metaObject); |
| const QMetaObject *superMetaObject = metaObject->superClass(); |
| if (!superMetaObject) { |
| prototypeName = "QObject"; |
| } else { |
| QQmlType superType = QQmlMetaType::qmlType(superMetaObject); |
| if (superType.isValid() && !superType.isComposite()) |
| return convertToId(superMetaObject->className()); |
| prototypeName = getPrototypeNameForCompositeType( |
| superMetaObject, objectsToMerge, versionInfo); |
| } |
| } else { |
| prototypeName = convertToId(metaObject->className()); |
| } |
| return prototypeName; |
| } |
| |
| void dumpComposite(QQmlEngine *engine, const QList<QQmlType> &compositeType, const QmlVersionInfo &versionInfo) |
| { |
| for (const QQmlType &type : compositeType) |
| dumpCompositeItem(engine, type, versionInfo); |
| } |
| |
| void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, const QmlVersionInfo &versionInfo) |
| { |
| QQmlComponent e(engine, compositeType.sourceUrl()); |
| if (!e.isReady()) { |
| std::cerr << "WARNING: skipping module " << compositeType.elementName().toStdString() |
| << std::endl << e.errorString().toStdString() << std::endl; |
| return; |
| } |
| |
| QObject *object = e.create(); |
| |
| if (!object) |
| return; |
| |
| qml->writeStartObject("Component"); |
| |
| const QMetaObject *mainMeta = object->metaObject(); |
| |
| QList<const QMetaObject *> objectsToMerge; |
| KnownAttributes knownAttributes; |
| // Get C++ base class name for the composite type |
| QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge, |
| versionInfo); |
| qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); |
| |
| QString qmlTyName = compositeType.qmlTypeName(); |
| const QString exportString = getExportString(compositeType, versionInfo); |
| |
| // TODO: why don't we simply output the compositeType.elementName() here? |
| // That would make more sense, but it would change the format quite a bit. |
| qml->writeScriptBinding(QLatin1String("name"), exportString); |
| |
| qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); |
| qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion())); |
| qml->writeBooleanBinding(QLatin1String("isComposite"), true); |
| |
| if (compositeType.isSingleton()) { |
| qml->writeBooleanBinding(QLatin1String("isCreatable"), false); |
| qml->writeBooleanBinding(QLatin1String("isSingleton"), true); |
| } |
| |
| for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) { |
| QMetaClassInfo classInfo = mainMeta->classInfo(index); |
| if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { |
| qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); |
| break; |
| } |
| } |
| |
| for (const QMetaObject *meta : qAsConst(objectsToMerge)) { |
| for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) |
| dump(meta->enumerator(index)); |
| |
| writeMetaContent(meta, &knownAttributes); |
| } |
| |
| qml->writeEndObject(); |
| } |
| |
| QString getDefaultProperty(const QMetaObject *meta) |
| { |
| for (int index = meta->classInfoCount() - 1; index >= 0; --index) { |
| QMetaClassInfo classInfo = meta->classInfo(index); |
| if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { |
| return QLatin1String(classInfo.value()); |
| } |
| } |
| return QString(); |
| } |
| |
| struct QmlTypeInfo { |
| QmlTypeInfo() {} |
| QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) |
| : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} |
| QString exportString; |
| int revision = 0; |
| const QMetaObject *extendedObject = nullptr; |
| QByteArray attachedTypeId; |
| }; |
| |
| void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton) |
| { |
| qml->writeStartObject("Component"); |
| |
| QByteArray id = convertToId(meta); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(id)); |
| |
| // collect type information |
| QVector<QmlTypeInfo> typeInfo; |
| for (QQmlType type : qmlTypesByCppName.value(meta->className())) { |
| const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; |
| QByteArray attachedTypeId; |
| if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { |
| // Can happen when a type is registered that returns itself as attachedPropertiesType() |
| // because there is no creatable type to attach to. |
| if (attachedType != meta) |
| attachedTypeId = convertToId(attachedType); |
| } |
| const QString exportString = getExportString(type, { QString(), -1, -1, false }); |
| int metaObjectRevision = type.metaObjectRevision(); |
| if (extendedObject) { |
| // emulate custom metaobjectrevision out of import |
| metaObjectRevision = type.majorVersion() * 100 + type.minorVersion(); |
| } |
| |
| QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId }; |
| typeInfo.append(info); |
| } |
| |
| // sort to ensure stable output |
| std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) { |
| return i1.revision < i2.revision; |
| }); |
| |
| // determine default property |
| // TODO: support revisioning of default property |
| QString defaultProperty = getDefaultProperty(meta); |
| if (defaultProperty.isEmpty()) { |
| for (const QmlTypeInfo &iter : typeInfo) { |
| if (iter.extendedObject) { |
| defaultProperty = getDefaultProperty(iter.extendedObject); |
| if (!defaultProperty.isEmpty()) |
| break; |
| } |
| } |
| } |
| if (!defaultProperty.isEmpty()) |
| qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); |
| |
| if (meta->superClass()) |
| qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); |
| |
| if (!typeInfo.isEmpty()) { |
| QMap<QString, QString> exports; // sort exports |
| for (const QmlTypeInfo &iter : typeInfo) |
| exports.insert(iter.exportString, QString::number(iter.revision)); |
| |
| QStringList exportStrings = exports.keys(); |
| QStringList metaObjectRevisions = exports.values(); |
| qml->writeArrayBinding(QLatin1String("exports"), exportStrings); |
| |
| if (isUncreatable) |
| qml->writeBooleanBinding(QLatin1String("isCreatable"), false); |
| |
| if (isSingleton) |
| qml->writeBooleanBinding(QLatin1String("isSingleton"), true); |
| |
| qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); |
| |
| for (const QmlTypeInfo &iter : typeInfo) { |
| if (!iter.attachedTypeId.isEmpty()) { |
| qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); |
| break; |
| } |
| } |
| } |
| |
| for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) |
| dump(meta->enumerator(index)); |
| |
| writeMetaContent(meta); |
| |
| // dump properties from extended metaobjects last |
| for (auto iter : typeInfo) { |
| if (iter.extendedObject) |
| dumpMetaProperties(iter.extendedObject, iter.revision); |
| } |
| |
| qml->writeEndObject(); |
| } |
| |
| void writeEasingCurve() |
| { |
| qml->writeStartObject(QLatin1String("Component")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve"))); |
| qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType"))); |
| qml->writeEndObject(); |
| } |
| |
| private: |
| |
| /* Removes pointer and list annotations from a type name, returning |
| what was removed in isList and isPointer |
| */ |
| static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer) |
| { |
| static QByteArray declListPrefix = "QQmlListProperty<"; |
| |
| if (typeName->endsWith('*')) { |
| *isPointer = true; |
| typeName->truncate(typeName->length() - 1); |
| removePointerAndList(typeName, isList, isPointer); |
| } else if (typeName->startsWith(declListPrefix)) { |
| *isList = true; |
| typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' |
| *typeName = typeName->mid(declListPrefix.size()); |
| removePointerAndList(typeName, isList, isPointer); |
| } |
| |
| *typeName = convertToId(*typeName); |
| } |
| |
| void writeTypeProperties(QByteArray typeName, bool isWritable) |
| { |
| bool isList = false, isPointer = false; |
| removePointerAndList(&typeName, &isList, &isPointer); |
| |
| qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); |
| if (isList) |
| qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); |
| if (!isWritable) |
| qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); |
| if (isPointer) |
| qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); |
| } |
| |
| void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) |
| { |
| int revision = metaRevision ? metaRevision : prop.revision(); |
| QByteArray propName = prop.name(); |
| if (knownAttributes && knownAttributes->knownProperty(propName, revision)) |
| return; |
| qml->writeStartObject("Property"); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); |
| if (revision) |
| qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); |
| writeTypeProperties(prop.typeName(), prop.isWritable()); |
| |
| qml->writeEndObject(); |
| } |
| |
| QSet<QString> dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) |
| { |
| QSet<QString> implicitSignals; |
| for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { |
| const QMetaProperty &property = meta->property(index); |
| dump(property, metaRevision, knownAttributes); |
| if (knownAttributes) |
| knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), |
| 0, property.revision()); |
| implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); |
| } |
| return implicitSignals; |
| } |
| |
| void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals, |
| KnownAttributes *knownAttributes = nullptr) |
| { |
| if (meth.methodType() == QMetaMethod::Signal) { |
| if (meth.access() != QMetaMethod::Public) |
| return; // nothing to do. |
| } else if (meth.access() != QMetaMethod::Public) { |
| return; // nothing to do. |
| } |
| |
| QByteArray name = meth.name(); |
| const QString typeName = convertToId(meth.typeName()); |
| |
| if (implicitSignals.contains(name) |
| && !meth.revision() |
| && meth.methodType() == QMetaMethod::Signal |
| && meth.parameterNames().isEmpty() |
| && typeName == QLatin1String("void")) { |
| // don't mention implicit signals |
| return; |
| } |
| |
| int revision = meth.revision(); |
| if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision)) |
| return; |
| if (meth.methodType() == QMetaMethod::Signal) |
| qml->writeStartObject(QLatin1String("Signal")); |
| else |
| qml->writeStartObject(QLatin1String("Method")); |
| |
| qml->writeScriptBinding(QLatin1String("name"), enquote(name)); |
| |
| if (revision) |
| qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); |
| |
| if (typeName != QLatin1String("void")) |
| qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); |
| |
| for (int i = 0; i < meth.parameterTypes().size(); ++i) { |
| QByteArray argName = meth.parameterNames().at(i); |
| |
| qml->writeStartObject(QLatin1String("Parameter")); |
| if (! argName.isEmpty()) |
| qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); |
| writeTypeProperties(meth.parameterTypes().at(i), true); |
| qml->writeEndObject(); |
| } |
| |
| qml->writeEndObject(); |
| } |
| |
| void dump(const QMetaEnum &e) |
| { |
| qml->writeStartObject(QLatin1String("Enum")); |
| qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); |
| |
| QList<QPair<QString, QString> > namesValues; |
| const int keyCount = e.keyCount(); |
| namesValues.reserve(keyCount); |
| for (int index = 0; index < keyCount; ++index) { |
| namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); |
| } |
| |
| qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); |
| qml->writeEndObject(); |
| } |
| }; |
| |
| enum ExitCode { |
| EXIT_INVALIDARGUMENTS = 1, |
| EXIT_SEGV = 2, |
| EXIT_IMPORTERROR = 3 |
| }; |
| |
| void printUsage(const QString &appName) |
| { |
| std::cerr << qPrintable(QString( |
| "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" |
| " %1 [-v] [-qapp] [-noinstantiate] -path path/to/qmldir/directory [version]\n" |
| " %1 [-v] -builtins\n" |
| "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg( |
| appName)) << std::endl; |
| } |
| |
| static bool readDependenciesData(QString dependenciesFile, const QByteArray &fileData, |
| QStringList *dependencies, const QStringList &urisToSkip, |
| bool forceQtQuickDependency = true) { |
| if (verbose) { |
| std::cerr << "parsing " |
| << qPrintable( dependenciesFile ) << " skipping"; |
| for (const QString &uriToSkip : urisToSkip) |
| std::cerr << ' ' << qPrintable(uriToSkip); |
| std::cerr << std::endl; |
| } |
| QJsonParseError parseError; |
| parseError.error = QJsonParseError::NoError; |
| QJsonDocument doc = QJsonDocument::fromJson(fileData, &parseError); |
| if (parseError.error != QJsonParseError::NoError) { |
| std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() |
| << ":" << parseError.errorString().toStdString() << " at " << parseError.offset |
| << std::endl; |
| return false; |
| } |
| if (doc.isArray()) { |
| const QStringList requiredKeys = QStringList() << QStringLiteral("name") |
| << QStringLiteral("type") |
| << QStringLiteral("version"); |
| const auto deps = doc.array(); |
| for (const QJsonValue &dep : deps) { |
| if (dep.isObject()) { |
| QJsonObject obj = dep.toObject(); |
| for (const QString &requiredKey : requiredKeys) |
| if (!obj.contains(requiredKey) || obj.value(requiredKey).isString()) |
| continue; |
| if (obj.value(QStringLiteral("type")).toString() != QLatin1String("module")) |
| continue; |
| QString name = obj.value((QStringLiteral("name"))).toString(); |
| QString version = obj.value(QStringLiteral("version")).toString(); |
| if (name.isEmpty() || urisToSkip.contains(name) || version.isEmpty()) |
| continue; |
| if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) { |
| if (verbose) |
| std::cerr << "skipping private dependecy " |
| << qPrintable( name ) << " " << qPrintable(version) << std::endl; |
| continue; |
| } |
| if (verbose) |
| std::cerr << "appending dependency " |
| << qPrintable( name ) << " " << qPrintable(version) << std::endl; |
| dependencies->append(name + QLatin1Char(' ')+version); |
| } |
| } |
| } else { |
| std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() |
| << ": expected an array" << std::endl; |
| return false; |
| } |
| // Workaround for avoiding conflicting types when no dependency has been found. |
| // |
| // qmlplugindump used to import QtQuick, so all types defined in QtQuick used to be skipped when dumping. |
| // Now that it imports only Qt, it is no longer the case: if no dependency is found all the types defined |
| // in QtQuick will be dumped, causing conflicts. |
| if (forceQtQuickDependency && dependencies->isEmpty()) |
| dependencies->push_back(qtQuickQualifiedName); |
| return true; |
| } |
| |
| static bool readDependenciesFile(const QString &dependenciesFile, QStringList *dependencies, |
| const QStringList &urisToSkip) { |
| if (!QFileInfo::exists(dependenciesFile)) { |
| std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() |
| << std::endl; |
| return false; |
| } |
| QFile f(dependenciesFile); |
| if (!f.open(QFileDevice::ReadOnly)) { |
| std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() |
| << ", " << f.errorString().toStdString() << std::endl; |
| return false; |
| } |
| QByteArray fileData = f.readAll(); |
| return readDependenciesData(dependenciesFile, fileData, dependencies, urisToSkip, false); |
| } |
| |
| static bool getDependencies(const QQmlEngine &engine, const QString &pluginImportUri, |
| const QString &pluginImportVersion, QStringList *dependencies, |
| bool forceQtQuickDependency) |
| { |
| QString importScannerExe = QLatin1String("qmlimportscanner"); |
| QFileInfo selfExe(QCoreApplication::applicationFilePath()); |
| if (!selfExe.suffix().isEmpty()) |
| importScannerExe += QLatin1String(".") + selfExe.suffix(); |
| QString command = selfExe.absoluteDir().filePath(importScannerExe); |
| |
| QStringList commandArgs = QStringList() |
| << QLatin1String("-qmlFiles") |
| << QLatin1String("-"); |
| QStringList importPathList = engine.importPathList(); |
| importPathList.removeOne(QStringLiteral("qrc:/qt-project.org/imports")); |
| for (const QString &path : importPathList) |
| commandArgs << QLatin1String("-importPath") << path; |
| |
| QProcess importScanner; |
| importScanner.start(command, commandArgs, QProcess::ReadWrite); |
| if (!importScanner.waitForStarted()) |
| return false; |
| |
| importScanner.write("import "); |
| importScanner.write(pluginImportUri.toUtf8()); |
| importScanner.write(" "); |
| importScanner.write(pluginImportVersion.toUtf8()); |
| importScanner.write("\nQtObject{}\n"); |
| importScanner.closeWriteChannel(); |
| |
| if (!importScanner.waitForFinished()) { |
| std::cerr << "failure to start " << qPrintable(command); |
| for (const QString &arg : qAsConst(commandArgs)) |
| std::cerr << ' ' << qPrintable(arg); |
| std::cerr << std::endl; |
| return false; |
| } |
| QByteArray depencenciesData = importScanner.readAllStandardOutput(); |
| if (!readDependenciesData(QLatin1String("<outputOfQmlimportscanner>"), depencenciesData, |
| dependencies, QStringList(pluginImportUri), forceQtQuickDependency)) { |
| std::cerr << "failed to process output of qmlimportscanner" << std::endl; |
| if (importScanner.exitCode() != 0) |
| std::cerr << importScanner.readAllStandardError().toStdString(); |
| return false; |
| } |
| |
| QStringList aux; |
| for (const QString &str : qAsConst(*dependencies)) { |
| if (!str.startsWith("Qt.test.qtestroot")) |
| aux += str; |
| } |
| *dependencies = aux; |
| |
| return true; |
| } |
| |
| bool compactDependencies(QStringList *dependencies) |
| { |
| if (dependencies->isEmpty()) |
| return false; |
| dependencies->sort(); |
| QStringList oldDep = dependencies->constFirst().split(QLatin1Char(' ')); |
| Q_ASSERT(oldDep.size() == 2); |
| int oldPos = 0; |
| for (int idep = 1; idep < dependencies->size(); ++idep) { |
| QString depStr = dependencies->at(idep); |
| const QStringList newDep = depStr.split(QLatin1Char(' ')); |
| Q_ASSERT(newDep.size() == 2); |
| if (newDep.constFirst() != oldDep.constFirst()) { |
| if (++oldPos != idep) |
| dependencies->replace(oldPos, depStr); |
| oldDep = newDep; |
| } else { |
| const QStringList v1 = oldDep.constLast().split(QLatin1Char('.')); |
| const QStringList v2 = newDep.constLast().split(QLatin1Char('.')); |
| Q_ASSERT(v1.size() == 2); |
| Q_ASSERT(v2.size() == 2); |
| bool ok; |
| int major1 = v1.first().toInt(&ok); |
| Q_ASSERT(ok); |
| int major2 = v2.first().toInt(&ok); |
| Q_ASSERT(ok); |
| if (major1 != major2) { |
| std::cerr << "Found a dependency on " << qPrintable(oldDep.constFirst()) |
| << " with two major versions:" << qPrintable(oldDep.constLast()) |
| << " and " << qPrintable(newDep.constLast()) |
| << " which is unsupported, discarding smaller version" << std::endl; |
| if (major1 < major2) |
| dependencies->replace(oldPos, depStr); |
| } else { |
| int minor1 = v1.last().toInt(&ok); |
| Q_ASSERT(ok); |
| int minor2 = v2.last().toInt(&ok); |
| Q_ASSERT(ok); |
| if (minor1 < minor2) |
| dependencies->replace(oldPos, depStr); |
| } |
| } |
| } |
| if (++oldPos < dependencies->size()) { |
| *dependencies = dependencies->mid(0, oldPos); |
| return true; |
| } |
| return false; |
| } |
| |
| inline std::wostream &operator<<(std::wostream &str, const QString &s) |
| { |
| #ifdef Q_OS_WIN |
| str << reinterpret_cast<const wchar_t *>(s.utf16()); |
| #else |
| str << s.toStdWString(); |
| #endif |
| return str; |
| } |
| |
| void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg) |
| { |
| std::wcerr << msg << std::endl; |
| // In case of QtFatalMsg the calling code will abort() when appropriate. |
| } |
| |
| QT_BEGIN_NAMESPACE |
| static bool operator<(const QQmlType &a, const QQmlType &b) |
| { |
| return a.qmlTypeName() < b.qmlTypeName() |
| || (a.qmlTypeName() == b.qmlTypeName() |
| && ((a.majorVersion() < b.majorVersion()) |
| || (a.majorVersion() == b.majorVersion() |
| && a.minorVersion() < b.minorVersion()))); |
| } |
| QT_END_NAMESPACE |
| |
| int main(int argc, char *argv[]) |
| { |
| #if defined(Q_OS_WIN) && !defined(Q_CC_MINGW) |
| // we do not want windows popping up if the module loaded triggers an assert |
| SetErrorMode(SEM_NOGPFAULTERRORBOX); |
| _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); |
| _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); |
| _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); |
| #endif // Q_OS_WIN && !Q_CC_MINGW |
| // The default message handler might not print to console on some systems. Enforce this. |
| qInstallMessageHandler(printDebugMessage); |
| |
| #ifdef QT_SIMULATOR |
| // Running this application would bring up the Qt Simulator (since it links Qt GUI), avoid that! |
| QtSimulatorPrivate::SimulatorConnection::createStubInstance(); |
| #endif |
| |
| // don't require a window manager even though we're a QGuiApplication |
| bool requireWindowManager = false; |
| for (int index = 1; index < argc; ++index) { |
| if (QString::fromLocal8Bit(argv[index]) == "--defaultplatform" |
| || QString::fromLocal8Bit(argv[index]) == "-defaultplatform") { |
| requireWindowManager = true; |
| break; |
| } |
| } |
| |
| if (!requireWindowManager && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) |
| qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); |
| else |
| QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); |
| |
| // Check which kind of application should be instantiated. |
| bool useQApplication = false; |
| for (int i = 0; i < argc; ++i) { |
| QString arg = QLatin1String(argv[i]); |
| if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) |
| useQApplication = true; |
| } |
| |
| #ifdef QT_WIDGETS_LIB |
| QScopedPointer<QCoreApplication> app(useQApplication |
| ? new QApplication(argc, argv) |
| : new QGuiApplication(argc, argv)); |
| #else |
| Q_UNUSED(useQApplication); |
| QScopedPointer<QCoreApplication> app(new QGuiApplication(argc, argv)); |
| #endif // QT_WIDGETS_LIB |
| |
| QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
| QStringList args = app->arguments(); |
| const QString appName = QFileInfo(app->applicationFilePath()).baseName(); |
| if (args.size() < 2) { |
| printUsage(appName); |
| return EXIT_INVALIDARGUMENTS; |
| } |
| |
| QString outputFilename; |
| QString pluginImportUri; |
| QString pluginImportVersion; |
| bool relocatable = true; |
| QString dependenciesFile; |
| QString mergeFile; |
| bool forceQtQuickDependency = true; |
| bool strict = false; |
| enum Action { Uri, Path, Builtins }; |
| Action action = Uri; |
| { |
| QStringList positionalArgs; |
| |
| for (int iArg = 0; iArg < args.size(); ++iArg) { |
| const QString &arg = args.at(iArg); |
| if (!arg.startsWith(QLatin1Char('-'))) { |
| positionalArgs.append(arg); |
| continue; |
| } |
| if (arg == QLatin1String("--dependencies") |
| || arg == QLatin1String("-dependencies")) { |
| if (++iArg == args.size()) { |
| std::cerr << "missing dependencies file" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| dependenciesFile = args.at(iArg); |
| |
| // Remove absolute path so that it does not show up in the |
| // printed command line inside the plugins.qmltypes file. |
| args[iArg] = QFileInfo(args.at(iArg)).fileName(); |
| } else if (arg == QLatin1String("--merge") |
| || arg == QLatin1String("-merge")) { |
| if (++iArg == args.size()) { |
| std::cerr << "missing merge file" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| mergeFile = args.at(iArg); |
| } else if (arg == QLatin1String("--notrelocatable") |
| || arg == QLatin1String("-notrelocatable") |
| || arg == QLatin1String("--nonrelocatable") |
| || arg == QLatin1String("-nonrelocatable")) { |
| relocatable = false; |
| } else if (arg == QLatin1String("--relocatable") |
| || arg == QLatin1String("-relocatable")) { |
| relocatable = true; |
| } else if (arg == QLatin1String("--noinstantiate") |
| || arg == QLatin1String("-noinstantiate")) { |
| creatable = false; |
| } else if (arg == QLatin1String("--path") |
| || arg == QLatin1String("-path")) { |
| action = Path; |
| } else if (arg == QLatin1String("--builtins") |
| || arg == QLatin1String("-builtins")) { |
| action = Builtins; |
| } else if (arg == QLatin1String("-v")) { |
| verbose = true; |
| } else if (arg == QLatin1String("--noforceqtquick") |
| || arg == QLatin1String("-noforceqtquick")){ |
| forceQtQuickDependency = false; |
| } else if (arg == QLatin1String("--output") |
| || arg == QLatin1String("-output")) { |
| if (++iArg == args.size()) { |
| std::cerr << "missing output file" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| outputFilename = args.at(iArg); |
| } else if (arg == QLatin1String("--defaultplatform") |
| || arg == QLatin1String("-defaultplatform")) { |
| continue; |
| } else if (arg == QLatin1String("--qapp") |
| || arg == QLatin1String("-qapp")) { |
| continue; |
| } else if (arg == QLatin1String("--strict") |
| || arg == QLatin1String("-strict")) { |
| strict = true; |
| continue; |
| } else { |
| std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| } |
| |
| if (action == Uri) { |
| if (positionalArgs.size() != 3 && positionalArgs.size() != 4) { |
| std::cerr << "Incorrect number of positional arguments" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| pluginImportUri = positionalArgs.at(1); |
| pluginImportVersion = positionalArgs[2]; |
| if (positionalArgs.size() >= 4) |
| pluginImportPath = positionalArgs.at(3); |
| } else if (action == Path) { |
| if (positionalArgs.size() != 2 && positionalArgs.size() != 3) { |
| std::cerr << "Incorrect number of positional arguments" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| pluginImportPath = QDir::fromNativeSeparators(positionalArgs.at(1)); |
| if (positionalArgs.size() == 3) |
| pluginImportVersion = positionalArgs.at(2); |
| } else if (action == Builtins) { |
| if (positionalArgs.size() != 1) { |
| std::cerr << "Incorrect number of positional arguments" << std::endl; |
| return EXIT_INVALIDARGUMENTS; |
| } |
| } |
| } |
| |
| QQmlEngine engine; |
| if (!pluginImportPath.isEmpty()) { |
| QDir cur = QDir::current(); |
| cur.cd(pluginImportPath); |
| pluginImportPath = cur.canonicalPath(); |
| QDir::setCurrent(pluginImportPath); |
| engine.addImportPath(pluginImportPath); |
| } |
| |
| // Merge file. |
| QStringList mergeDependencies; |
| QString mergeComponents; |
| if (!mergeFile.isEmpty()) { |
| const QStringList merge = readQmlTypes(mergeFile); |
| if (!merge.isEmpty()) { |
| QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); |
| QRegularExpressionMatchIterator i = re.globalMatch(merge[1]); |
| while (i.hasNext()) { |
| QRegularExpressionMatch m = i.next(); |
| mergeDependencies << m.captured(1); |
| } |
| mergeComponents = merge [2]; |
| } |
| } |
| |
| // Dependencies. |
| |
| bool calculateDependencies = !pluginImportUri.isEmpty() && !pluginImportVersion.isEmpty(); |
| QStringList dependencies; |
| if (!dependenciesFile.isEmpty()) |
| calculateDependencies = !readDependenciesFile(dependenciesFile, &dependencies, |
| QStringList(pluginImportUri)) && calculateDependencies; |
| if (calculateDependencies) |
| getDependencies(engine, pluginImportUri, pluginImportVersion, &dependencies, |
| forceQtQuickDependency); |
| |
| compactDependencies(&dependencies); |
| |
| |
| QString qtQmlImportString = QString::fromLatin1("import QtQml %1.%2") |
| .arg(qtQmlMajorVersion) |
| .arg(qtQmlMinorVersion); |
| |
| // load the QtQml builtins and the dependencies |
| { |
| QByteArray code(qtQmlImportString.toUtf8()); |
| for (const QString &moduleToImport : qAsConst(dependencies)) { |
| code.append("\nimport "); |
| code.append(moduleToImport.toUtf8()); |
| } |
| code.append("\nQtObject {}"); |
| QQmlComponent c(&engine); |
| c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loaddependencies.qml")); |
| c.create(); |
| const auto errors = c.errors(); |
| if (!errors.isEmpty()) { |
| for (const QQmlError &error : errors) |
| std::cerr << qPrintable( error.toString() ) << std::endl; |
| return EXIT_IMPORTERROR; |
| } |
| } |
| |
| // find all QMetaObjects reachable from the builtin module |
| QSet<const QMetaObject *> uncreatableMetas; |
| QSet<const QMetaObject *> singletonMetas; |
| |
| // this will hold the meta objects we want to dump information of |
| QSet<const QMetaObject *> metas; |
| |
| // composite types we want to dump information of |
| QMap<QString, QList<QQmlType>> compositeTypes; |
| |
| int majorVersion = qtQmlMajorVersion, minorVersion = qtQmlMinorVersion; |
| QmlVersionInfo info; |
| if (action == Builtins) { |
| QMap<QString, QList<QQmlType>> defaultCompositeTypes; |
| QSet<const QMetaObject *> builtins = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes, {QLatin1String("Qt"), majorVersion, minorVersion, strict}); |
| Q_ASSERT(builtins.size() == 1); |
| metas.insert(*builtins.begin()); |
| } else { |
| auto versionSplitted = pluginImportVersion.split("."); |
| bool ok = versionSplitted.size() == 2; |
| if (!ok) |
| qCritical("Invalid version number"); |
| else { |
| majorVersion = versionSplitted.at(0).toInt(&ok); |
| if (!ok) |
| qCritical("Invalid major version"); |
| minorVersion = versionSplitted.at(1).toInt(&ok); |
| if (!ok) |
| qCritical("Invalid minor version"); |
| } |
| QList<QQmlType> defaultTypes = QQmlMetaType::qmlTypes(); |
| // find a valid QtQuick import |
| QByteArray importCode; |
| QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject); |
| if (!qtObjectType.isValid()) { |
| std::cerr << "Could not find QtObject type" << std::endl; |
| importCode = qtQmlImportString.toUtf8(); |
| } else { |
| QString module = qtObjectType.qmlTypeName(); |
| module = module.mid(0, module.lastIndexOf(QLatin1Char('/'))); |
| importCode = QString("import %1 %2.%3").arg(module, |
| QString::number(qtObjectType.majorVersion()), |
| QString::number(qtObjectType.minorVersion())).toUtf8(); |
| } |
| // avoid importing dependencies? |
| for (const QString &moduleToImport : qAsConst(dependencies)) { |
| importCode.append("\nimport "); |
| importCode.append(moduleToImport.toUtf8()); |
| } |
| |
| // find all QMetaObjects reachable when the specified module is imported |
| if (action != Path) { |
| importCode += QString("\nimport %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1(); |
| } else { |
| // pluginImportVersion can be empty |
| importCode += QString("\nimport \".\" %2\n").arg(pluginImportVersion).toLatin1(); |
| } |
| |
| // create a component with these imports to make sure the imports are valid |
| // and to populate the declarative meta type system |
| { |
| QByteArray code = importCode; |
| code += "\nQtObject {}"; |
| QQmlComponent c(&engine); |
| |
| c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml")); |
| c.create(); |
| const auto errors = c.errors(); |
| if (!errors.isEmpty()) { |
| for (const QQmlError &error : errors) |
| std::cerr << qPrintable( error.toString() ) << std::endl; |
| return EXIT_IMPORTERROR; |
| } |
| } |
| info = {pluginImportUri, majorVersion, minorVersion, strict}; |
| QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, info, defaultTypes); |
| |
| for (auto it = compositeTypes.begin(), end = compositeTypes.end(); it != end; ++it) { |
| std::sort(it->begin(), it->end()); |
| it->erase(std::unique(it->begin(), it->end()), it->end()); |
| } |
| |
| for (const QMetaObject *mo : qAsConst(candidates)) { |
| if (mo->className() != QLatin1String("Qt")) |
| metas.insert(mo); |
| } |
| } |
| |
| // setup static rewrites of type names |
| cppToId.insert("QString", "string"); |
| cppToId.insert("QQmlEasingValueType::Type", "Type"); |
| |
| // start dumping data |
| QByteArray bytes; |
| QmlStreamWriter qml(&bytes); |
| |
| qml.writeStartDocument(); |
| qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); |
| qml.write(QString("\n" |
| "// This file describes the plugin-supplied types contained in the library.\n" |
| "// It is used for QML tooling purposes only.\n" |
| "//\n" |
| "// This file was auto-generated by:\n" |
| "// '%1 %2'\n" |
| "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' ')))); |
| qml.writeStartObject("Module"); |
| |
| // Insert merge dependencies. |
| if (!mergeDependencies.isEmpty()) { |
| dependencies << mergeDependencies; |
| } |
| compactDependencies(&dependencies); |
| |
| QStringList quotedDependencies; |
| for (const QString &dep : qAsConst(dependencies)) |
| quotedDependencies << enquote(dep); |
| qml.writeArrayBinding("dependencies", quotedDependencies); |
| |
| // put the metaobjects into a map so they are always dumped in the same order |
| QMap<QString, const QMetaObject *> nameToMeta; |
| for (const QMetaObject *meta : qAsConst(metas)) |
| nameToMeta.insert(convertToId(meta), meta); |
| |
| Dumper dumper(&qml); |
| if (relocatable) |
| dumper.setRelocatableModuleUri(pluginImportUri); |
| for (const QMetaObject *meta : qAsConst(nameToMeta)) { |
| dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); |
| } |
| |
| QMap<QString, QList<QQmlType>>::const_iterator iter = compositeTypes.constBegin(); |
| for (; iter != compositeTypes.constEnd(); ++iter) |
| dumper.dumpComposite(&engine, iter.value(), info); |
| |
| // define QEasingCurve as an extension of QQmlEasingValueType, this way |
| // properties using the QEasingCurve type get useful type information. |
| if (pluginImportUri.isEmpty()) |
| dumper.writeEasingCurve(); |
| |
| // Insert merge file. |
| qml.write(mergeComponents); |
| |
| qml.writeEndObject(); |
| qml.writeEndDocument(); |
| |
| if (!outputFilename.isEmpty()) { |
| QFile file(outputFilename); |
| if (file.open(QIODevice::WriteOnly)) { |
| QTextStream stream(&file); |
| stream << bytes.constData(); |
| } |
| } else { |
| std::cout << bytes.constData() << std::flush; |
| } |
| |
| // workaround to avoid crashes on exit |
| QTimer timer; |
| timer.setSingleShot(true); |
| timer.setInterval(0); |
| QObject::connect(&timer, SIGNAL(timeout()), app.data(), SLOT(quit())); |
| timer.start(); |
| |
| return app->exec(); |
| } |