diff options
Diffstat (limited to 'src/libs/qmljs/qmljsplugindumper.cpp')
-rw-r--r-- | src/libs/qmljs/qmljsplugindumper.cpp | 341 |
1 files changed, 249 insertions, 92 deletions
diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index 7ba665dc94..8b7604809b 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -34,6 +34,7 @@ #include <utils/filesystemwatcher.h> #include <utils/fileutils.h> #include <utils/hostosinfo.h> +#include <utils/runextensions.h> #include <QDir> #include <QDirIterator> @@ -283,35 +284,57 @@ void PluginDumper::qmlPluginTypeDumpDone(int exitCode) if (!privatePlugin) ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages)); libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages)); - } - - const QByteArray output = process->readAllStandardOutput(); - QString error; - QString warning; - CppQmlTypesLoader::BuiltinObjects objectsList; - QList<ModuleApiInfo> moduleApis; - QStringList dependencies; - CppQmlTypesLoader::parseQmlTypeDescriptions(output, &objectsList, &moduleApis, &dependencies, - &error, &warning, - QLatin1String("<dump of ") + libraryPath + QLatin1Char('>')); - if (exitCode == 0) { - if (!error.isEmpty()) { - libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, - qmldumpErrorMessage(libraryPath, error)); - if (!privatePlugin) - printParseWarnings(libraryPath, libraryInfo.pluginTypeInfoError()); - } else { - libraryInfo.setMetaObjects(objectsList.values()); - libraryInfo.setModuleApis(moduleApis); - libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone); - } - if (!warning.isEmpty()) - printParseWarnings(libraryPath, warning); + const QByteArray output = process->readAllStandardOutput(); + + class CppQmlTypesInfo { + public: + QString error; + QString warning; + CppQmlTypesLoader::BuiltinObjects objectsList; + QList<ModuleApiInfo> moduleApis; + QStringList dependencies; + }; + + auto future = Utils::runAsync([output, libraryPath](QFutureInterface<CppQmlTypesInfo>& future) + { + CppQmlTypesInfo infos; + CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, &infos.moduleApis, &infos.dependencies, + &infos.error, &infos.warning, + QLatin1String("<dump of ") + libraryPath + QLatin1Char('>')); + future.reportFinished(&infos); + }); + + auto finalFuture = Utils::onFinished(future, this, + [this, libraryInfo, privatePlugin, libraryPath] (const QFuture<CppQmlTypesInfo>& future) { + CppQmlTypesInfo infos = future.result(); + + LibraryInfo libInfo = libraryInfo; + + if (!infos.error.isEmpty()) { + libInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, + qmldumpErrorMessage(libraryPath, infos.error)); + if (!privatePlugin) + printParseWarnings(libraryPath, libInfo.pluginTypeInfoError()); + } else { + libInfo.setMetaObjects(infos.objectsList.values()); + libInfo.setModuleApis(infos.moduleApis); + libInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone); + } + + if (!infos.warning.isEmpty()) + printParseWarnings(libraryPath, infos.warning); + + libInfo.updateFingerprint(); + + m_modelManager->updateLibraryInfo(libraryPath, libInfo); + }); + m_modelManager->addFuture(finalFuture); + } else { + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone); + libraryInfo.updateFingerprint(); + m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); } - libraryInfo.updateFingerprint(); - - m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); } void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError) @@ -344,38 +367,42 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary) dump(plugin); } -void PluginDumper::loadQmlTypeDescription(const QStringList &paths, - QStringList &errors, - QStringList &warnings, - QList<FakeMetaObject::ConstPtr> &objects, - QList<ModuleApiInfo> *moduleApi, - QStringList *dependencies) const { - for (const QString &p: paths) { - Utils::FileReader reader; - if (!reader.fetch(p, QFile::Text)) { - errors += reader.errorString(); - continue; - } - QString error; - QString warning; - CppQmlTypesLoader::BuiltinObjects objs; - QList<ModuleApiInfo> apis; - QStringList deps; - CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &objs, &apis, &deps, - &error, &warning, p); - if (!error.isEmpty()) { - errors += tr("Failed to parse \"%1\".\nError: %2").arg(p, error); - } else { - objects += objs.values(); - if (moduleApi) - *moduleApi += apis; - if (!deps.isEmpty()) - *dependencies += deps; +QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(const QStringList &paths) const { + auto future = Utils::runAsync([=](QFutureInterface<PluginDumper::QmlTypeDescription> &future) + { + PluginDumper::QmlTypeDescription result; + + for (const QString &p: paths) { + Utils::FileReader reader; + if (!reader.fetch(p, QFile::Text)) { + result.errors += reader.errorString(); + continue; + } + QString error; + QString warning; + CppQmlTypesLoader::BuiltinObjects objs; + QList<ModuleApiInfo> apis; + QStringList deps; + CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &objs, &apis, &deps, + &error, &warning, p); + if (!error.isEmpty()) { + result.errors += tr("Failed to parse \"%1\".\nError: %2").arg(p, error); + } else { + result.objects += objs.values(); + result.moduleApis += apis; + if (!deps.isEmpty()) + result.dependencies += deps; + } + if (!warning.isEmpty()) + result.warnings += warning; } - if (!warning.isEmpty()) - warnings += warning; - } + + future.reportFinished(&result); + }); + + return future; } + /*! * \brief Build the path of an existing qmltypes file from a module name. * \param name @@ -421,16 +448,14 @@ QString PluginDumper::buildQmltypesPath(const QString &name) const * Recursively load type descriptions of dependencies, collecting results * in \a objects. */ -void PluginDumper::loadDependencies(const QStringList &dependencies, - QStringList &errors, - QStringList &warnings, - QList<FakeMetaObject::ConstPtr> &objects, - QSet<QString> *visited) const +QFuture<PluginDumper::DependencyInfo> PluginDumper::loadDependencies(const QStringList &dependencies, + QSharedPointer<QSet<QString>> visited) const { - if (dependencies.isEmpty()) - return; + auto iface = QSharedPointer<QFutureInterface<PluginDumper::DependencyInfo>>(new QFutureInterface<PluginDumper::DependencyInfo>); - QScopedPointer<QSet<QString>> visitedPtr(visited ? visited : new QSet<QString>()); + if (visited.isNull()) { + visited = QSharedPointer<QSet<QString>>(new QSet<QString>()); + } QStringList dependenciesPaths; QString path; @@ -438,44 +463,176 @@ void PluginDumper::loadDependencies(const QStringList &dependencies, path = buildQmltypesPath(name); if (!path.isNull()) dependenciesPaths << path; - visitedPtr->insert(name); + visited->insert(name); } - QStringList newDependencies; - loadQmlTypeDescription(dependenciesPaths, errors, warnings, objects, nullptr, &newDependencies); - newDependencies = Utils::toList(Utils::toSet(newDependencies) - *visitedPtr); - if (!newDependencies.isEmpty()) - loadDependencies(newDependencies, errors, warnings, objects, visitedPtr.take()); + + Utils::onFinished(loadQmlTypeDescription(dependenciesPaths), const_cast<PluginDumper*>(this), [=] (const QFuture<PluginDumper::QmlTypeDescription> &typesFuture) { + PluginDumper::QmlTypeDescription typesResult = typesFuture.result(); + QStringList newDependencies = typesResult.dependencies; + newDependencies = Utils::toList(Utils::toSet(newDependencies) - *visited.data()); + if (!newDependencies.isEmpty()) { + Utils::onFinished(loadDependencies(newDependencies, visited), + const_cast<PluginDumper*>(this), [typesResult, iface] (const QFuture<PluginDumper::DependencyInfo> &future) { + PluginDumper::DependencyInfo result = future.result(); + + result.errors += typesResult.errors; + result.objects += typesResult.objects; + result.warnings+= typesResult.warnings; + + iface->reportFinished(&result); + }); + + } else { + PluginDumper::DependencyInfo result; + result.errors += typesResult.errors; + result.objects += typesResult.objects; + result.warnings+= typesResult.warnings; + iface->reportFinished(&result); + } + }); + + return iface->future(); } -void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths, - const QString &libraryPath, - QmlJS::LibraryInfo libraryInfo) +// Fills \a highestVersion with the largest export version for \a package +// and sets \a hasExportName to true if a type called \a exportName is found. +static void getHighestExportVersion( + const QList<LanguageUtils::FakeMetaObject::ConstPtr> &objects, + const QString &package, + const QString &exportName, + bool *hasExportName, + ComponentVersion *highestVersion) +{ + *highestVersion = ComponentVersion(); + *hasExportName = false; + for (const auto &object : objects) { + for (const auto &e : object->exports()) { + if (e.package == package) { + if (e.version > *highestVersion) + *highestVersion = e.version; + if (e.type == exportName) + *hasExportName = true; + } + } + } + +} + +/*** Workaround for implicit dependencies in >= 5.15.0. + * + * When "QtQuick" is imported, "QtQml" is implicitly loaded as well. + * When "QtQml" is imported, "QtQml.Models" and "QtQml.WorkerScript" are implicitly loaded. + * Add these imports as if they were "import" commands in the qmldir file. + * + * Qt 6 is planned to have these included in the qmldir file. + */ +static void applyQt515MissingImportWorkaround(const QString &path, LibraryInfo &info) { - QStringList errors; - QStringList warnings; - QList<FakeMetaObject::ConstPtr> objects; - QList<ModuleApiInfo> moduleApis; - QStringList dependencies; - - loadQmlTypeDescription(qmltypesFilePaths, errors, warnings, objects, &moduleApis, &dependencies); - loadDependencies(dependencies, errors, warnings, objects); - - libraryInfo.setMetaObjects(objects); - libraryInfo.setModuleApis(moduleApis); - libraryInfo.setDependencies(dependencies); - if (errors.isEmpty()) { - libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone); + if (!info.imports().isEmpty()) + return; + + const bool isQtQuick = path.endsWith(QStringLiteral("/QtQuick")) + || path.endsWith(QStringLiteral("/QtQuick.2")); + const bool isQtQml = path.endsWith(QStringLiteral("/QtQml")) + || path.endsWith(QStringLiteral("/QtQml.2")); + if (!isQtQuick && !isQtQml) + return; + + ComponentVersion highestVersion; + const auto package = isQtQuick ? QStringLiteral("QtQuick") : QStringLiteral("QtQml"); + const auto missingTypeName = isQtQuick ? QStringLiteral("QtObject") : QStringLiteral("ListElement"); + bool hasMissingType = false; + getHighestExportVersion( + info.metaObjects(), + package, + missingTypeName, + &hasMissingType, + &highestVersion); + + // If the highest export version is < 2.15, we expect Qt <5.15 + if (highestVersion.majorVersion() != 2 || highestVersion.minorVersion() < 15) + return; + // As an extra sanity check: if the type from the dependent module already exists, + // don't proceeed either. + if (hasMissingType) + return; + + if (isQtQuick) { + info.setImports(QStringList(QStringLiteral("QtQml"))); + } else if (isQtQml) { + info.setImports(QStringList( + { QStringLiteral("QtQml.Models"), + QStringLiteral("QtQml.WorkerScript") })); + } +} + +void PluginDumper::prepareLibraryInfo(LibraryInfo &libInfo, + const QString &libraryPath, + const QStringList &deps, + const QStringList &errors, + const QStringList &warnings, + const QList<ModuleApiInfo> &moduleApis, + QList<FakeMetaObject::ConstPtr> &objects) +{ + QStringList errs = errors; + + libInfo.setMetaObjects(objects); + libInfo.setModuleApis(moduleApis); + libInfo.setDependencies(deps); + + if (errs.isEmpty()) { + libInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone); } else { printParseWarnings(libraryPath, errors.join(QLatin1Char('\n'))); - errors.prepend(tr("Errors while reading typeinfo files:")); - libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errors.join(QLatin1Char('\n'))); + errs.prepend(tr("Errors while reading typeinfo files:")); + libInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errs.join(QLatin1Char('\n'))); } if (!warnings.isEmpty()) printParseWarnings(libraryPath, warnings.join(QLatin1String("\n"))); - libraryInfo.updateFingerprint(); - m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); + applyQt515MissingImportWorkaround(libraryPath, libInfo); + + libInfo.updateFingerprint(); +} + +void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths, + const QString &libraryPath, + QmlJS::LibraryInfo libraryInfo) +{ + auto future = Utils::onFinished(loadQmlTypeDescription(qmltypesFilePaths), this, [=](const QFuture<PluginDumper::QmlTypeDescription> &typesFuture) + { + PluginDumper::QmlTypeDescription typesResult = typesFuture.result(); + if (!typesResult.dependencies.isEmpty()) + { + auto depFuture = Utils::onFinished(loadDependencies(typesResult.dependencies, QSharedPointer<QSet<QString>>()), this, + [typesResult, libraryInfo, libraryPath, this] (const QFuture<PluginDumper::DependencyInfo> &loadFuture) + { + PluginDumper::DependencyInfo loadResult = loadFuture.result(); + QStringList errors = typesResult.errors; + QStringList warnings = typesResult.errors; + QList<FakeMetaObject::ConstPtr> objects = typesResult.objects; + + errors += loadResult.errors; + warnings += loadResult.warnings; + objects += loadResult.objects; + + QmlJS::LibraryInfo libInfo = libraryInfo; + prepareLibraryInfo(libInfo, libraryPath, typesResult.dependencies, + errors, warnings, + typesResult.moduleApis, objects); + m_modelManager->updateLibraryInfo(libraryPath, libInfo); + }); + m_modelManager->addFuture(depFuture); + } else { + QmlJS::LibraryInfo libInfo = libraryInfo; + prepareLibraryInfo(libInfo, libraryPath, typesResult.dependencies, + typesResult.errors, typesResult.warnings, + typesResult.moduleApis, typesResult.objects); + m_modelManager->updateLibraryInfo(libraryPath, libInfo); + } + }); + m_modelManager->addFuture(future); } void PluginDumper::runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info, |