/**************************************************************************** ** ** Copyright (C) 2021 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:COMM$ ** ** 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. ** ** $QT_END_LICENSE$ ** ** ** ** ** ** ** ** ****************************************************************************/ #include #include #include #include #include #include #ifdef QT_WIDGETS_LIB #include #endif // QT_WIDGETS_LIB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmltypereader.h" #include "qmlstreamwriter.h" #ifdef QT_SIMULATOR #include #endif #ifdef Q_OS_WIN # if !defined(Q_CC_MINGW) # include # endif #include #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) { const QString &module = ty.module(); if (versionInfo.strict) { return (versionInfo.pluginImportUri == module && (ty.majorVersion() == versionInfo.majorVersion || ty.majorVersion() == -1)) || module.isEmpty(); } return module.isEmpty() || versionInfo.pluginImportUri == module || module.startsWith(versionInfo.pluginImportUri + QLatin1Char('.')); } void collectReachableMetaObjects(const QMetaObject *meta, QSet *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(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 *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 *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 > qmlTypesByCppName; static QHash 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 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& metas, QMap> &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 collectReachableMetaObjects(QQmlEngine *engine, QSet &noncreatables, QSet &singletons, QMap> &compositeTypes, const QmlVersionInfo &info, const QList &skip = QList() ) { QSet 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(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 m_properties; QHash > m_methods; public: bool knownMethod(const QByteArray &name, int nArgs, int revision) { if (m_methods.contains(name)) { QHash 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 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 *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(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 &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 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 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 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 dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { QSet 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 &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 > 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 ] [-merge ] [-output ] [-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(""), 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(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 app(useQApplication ? new QApplication(argc, argv) : new QGuiApplication(argc, argv)); #else Q_UNUSED(useQApplication); QScopedPointer 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 uncreatableMetas; QSet singletonMetas; // this will hold the meta objects we want to dump information of QSet metas; // composite types we want to dump information of QMap> compositeTypes; int majorVersion = qtQmlMajorVersion, minorVersion = qtQmlMinorVersion; QmlVersionInfo info; if (action == Builtins) { QMap> defaultCompositeTypes; QSet 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 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 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 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>::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(); }