qmljs: Make the QMLJsPluginDumper asynchronous to avoid eventloop hangs

Change-Id: I3f6e6acaaf3781d86a0fa5fb100219f92b70f0b5
Fixes: QTCREATORBUG-20243
Task-number: QTCREATORBUG-18533
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
This commit is contained in:
Philip Van Hoof
2020-04-17 11:40:42 +02:00
parent b1352fb044
commit ba171172e4
2 changed files with 182 additions and 102 deletions

View File

@@ -34,6 +34,7 @@
#include <utils/filesystemwatcher.h> #include <utils/filesystemwatcher.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/runextensions.h>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
@@ -283,36 +284,61 @@ void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
if (!privatePlugin) if (!privatePlugin)
ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages)); ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages));
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages)); libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages));
}
const QByteArray output = process->readAllStandardOutput(); const QByteArray output = process->readAllStandardOutput();
class CppQmlTypesInfo {
public:
QString error; QString error;
QString warning; QString warning;
CppQmlTypesLoader::BuiltinObjects objectsList; CppQmlTypesLoader::BuiltinObjects objectsList;
QList<ModuleApiInfo> moduleApis; QList<ModuleApiInfo> moduleApis;
QStringList dependencies; QStringList dependencies;
CppQmlTypesLoader::parseQmlTypeDescriptions(output, &objectsList, &moduleApis, &dependencies, };
&error, &warning,
QLatin1String("<dump of ") + libraryPath + QLatin1Char('>')); auto watcher = QSharedPointer<QFutureWatcher<CppQmlTypesInfo>>(new QFutureWatcher<CppQmlTypesInfo>());
if (exitCode == 0) {
if (!error.isEmpty()) { connect(watcher.data(), &QFutureWatcher<CppQmlTypesInfo>::finished, this,
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, [this, watcher, libraryInfo, privatePlugin, libraryPath] {
qmldumpErrorMessage(libraryPath, error)); CppQmlTypesInfo infos = watcher->result();
LibraryInfo libInfo = libraryInfo;
if (!infos.error.isEmpty()) {
libInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError,
qmldumpErrorMessage(libraryPath, infos.error));
if (!privatePlugin) if (!privatePlugin)
printParseWarnings(libraryPath, libraryInfo.pluginTypeInfoError()); 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);
});
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);
});
watcher->setFuture(future);
} else { } else {
libraryInfo.setMetaObjects(objectsList.values());
libraryInfo.setModuleApis(moduleApis);
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone); libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone);
}
if (!warning.isEmpty())
printParseWarnings(libraryPath, warning);
}
libraryInfo.updateFingerprint(); libraryInfo.updateFingerprint();
m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
} }
}
void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError) void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError)
{ {
@@ -344,16 +370,15 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary)
dump(plugin); dump(plugin);
} }
void PluginDumper::loadQmlTypeDescription(const QStringList &paths, QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(const QStringList &paths) const {
QStringList &errors, auto future = Utils::runAsync([=](QFutureInterface<PluginDumper::QmlTypeDescription> &future)
QStringList &warnings, {
QList<FakeMetaObject::ConstPtr> &objects, PluginDumper::QmlTypeDescription result;
QList<ModuleApiInfo> *moduleApi,
QStringList *dependencies) const {
for (const QString &p: paths) { for (const QString &p: paths) {
Utils::FileReader reader; Utils::FileReader reader;
if (!reader.fetch(p, QFile::Text)) { if (!reader.fetch(p, QFile::Text)) {
errors += reader.errorString(); result.errors += reader.errorString();
continue; continue;
} }
QString error; QString error;
@@ -364,18 +389,23 @@ void PluginDumper::loadQmlTypeDescription(const QStringList &paths,
CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &objs, &apis, &deps, CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &objs, &apis, &deps,
&error, &warning, p); &error, &warning, p);
if (!error.isEmpty()) { if (!error.isEmpty()) {
errors += tr("Failed to parse \"%1\".\nError: %2").arg(p, error); result.errors += tr("Failed to parse \"%1\".\nError: %2").arg(p, error);
} else { } else {
objects += objs.values(); result.objects += objs.values();
if (moduleApi) result.moduleApis += apis;
*moduleApi += apis;
if (!deps.isEmpty()) if (!deps.isEmpty())
*dependencies += deps; result.dependencies += deps;
} }
if (!warning.isEmpty()) if (!warning.isEmpty())
warnings += warning; result.warnings += warning;
} }
future.reportFinished(&result);
});
return future;
} }
/*! /*!
* \brief Build the path of an existing qmltypes file from a module name. * \brief Build the path of an existing qmltypes file from a module name.
* \param name * \param name
@@ -421,16 +451,14 @@ QString PluginDumper::buildQmltypesPath(const QString &name) const
* Recursively load type descriptions of dependencies, collecting results * Recursively load type descriptions of dependencies, collecting results
* in \a objects. * in \a objects.
*/ */
void PluginDumper::loadDependencies(const QStringList &dependencies, QFuture<PluginDumper::DependencyInfo> PluginDumper::loadDependencies(const QStringList &dependencies,
QStringList &errors, QSharedPointer<QSet<QString>> visited) const
QStringList &warnings,
QList<FakeMetaObject::ConstPtr> &objects,
QSet<QString> *visited) const
{ {
if (dependencies.isEmpty()) auto iface = QSharedPointer<QFutureInterface<PluginDumper::DependencyInfo>>(new QFutureInterface<PluginDumper::DependencyInfo>);
return;
QScopedPointer<QSet<QString>> visitedPtr(visited ? visited : new QSet<QString>()); if (visited.isNull()) {
visited = QSharedPointer<QSet<QString>>(new QSet<QString>());
}
QStringList dependenciesPaths; QStringList dependenciesPaths;
QString path; QString path;
@@ -438,44 +466,84 @@ void PluginDumper::loadDependencies(const QStringList &dependencies,
path = buildQmltypesPath(name); path = buildQmltypesPath(name);
if (!path.isNull()) if (!path.isNull())
dependenciesPaths << path; dependenciesPaths << path;
visitedPtr->insert(name); visited->insert(name);
} }
QStringList newDependencies;
loadQmlTypeDescription(dependenciesPaths, errors, warnings, objects, nullptr, &newDependencies); auto typesWatcher = QSharedPointer<QFutureWatcher<PluginDumper::QmlTypeDescription>>(new QFutureWatcher<PluginDumper::QmlTypeDescription>());
newDependencies = Utils::toList(Utils::toSet(newDependencies) - *visitedPtr); connect(typesWatcher.data(), &QFutureWatcher<PluginDumper::QmlTypeDescription>::finished, this, [this, iface, visited, typesWatcher] {
if (!newDependencies.isEmpty()) QStringList newDependencies = typesWatcher->result().dependencies;
loadDependencies(newDependencies, errors, warnings, objects, visitedPtr.take()); newDependencies = Utils::toList(Utils::toSet(newDependencies) - *visited.data());
if (!newDependencies.isEmpty()) {
auto loadWatcher = QSharedPointer<QFutureWatcher<PluginDumper::DependencyInfo>>(new QFutureWatcher<PluginDumper::DependencyInfo>());
connect(loadWatcher.data(), &QFutureWatcher<PluginDumper::DependencyInfo>::finished, this, [iface, newDependencies, visited, typesWatcher, loadWatcher] {
PluginDumper::DependencyInfo result = loadWatcher->result();
result.errors += typesWatcher->result().errors;
result.objects += typesWatcher->result().objects;
result.warnings+= typesWatcher->result().warnings;
iface->reportFinished(&result);
});
loadWatcher->setFuture(loadDependencies(newDependencies, visited));
} else {
PluginDumper::DependencyInfo result;
result.errors += typesWatcher->result().errors;
result.objects += typesWatcher->result().objects;
result.warnings+= typesWatcher->result().warnings;
iface->reportFinished(&result);
}
});
typesWatcher->setFuture(loadQmlTypeDescription(dependenciesPaths));
return iface->future();
} }
void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths, void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths,
const QString &libraryPath, const QString &libraryPath,
QmlJS::LibraryInfo libraryInfo) QmlJS::LibraryInfo libraryInfo)
{ {
QStringList errors; auto typesWatcher = QSharedPointer<QFutureWatcher<PluginDumper::QmlTypeDescription>>(new QFutureWatcher<PluginDumper::QmlTypeDescription>());
QStringList warnings; connect(typesWatcher.data(), &QFutureWatcher<PluginDumper::QmlTypeDescription>::finished, this, [this, typesWatcher, libraryPath, libraryInfo] {
QList<FakeMetaObject::ConstPtr> objects;
QList<ModuleApiInfo> moduleApis;
QStringList dependencies;
loadQmlTypeDescription(qmltypesFilePaths, errors, warnings, objects, &moduleApis, &dependencies); auto loadWatcher = QSharedPointer<QFutureWatcher<PluginDumper::DependencyInfo>>(new QFutureWatcher<PluginDumper::DependencyInfo>());
loadDependencies(dependencies, errors, warnings, objects); connect(loadWatcher.data(), &QFutureWatcher<PluginDumper::DependencyInfo>::finished, this, [this, typesWatcher, loadWatcher, libraryPath, libraryInfo] {
QStringList deps = typesWatcher->result().dependencies;
QStringList errors = typesWatcher->result().errors;
QStringList warnings = typesWatcher->result().errors;
QList<FakeMetaObject::ConstPtr> objects = typesWatcher->result().objects;
errors += loadWatcher->result().errors;
warnings += loadWatcher->result().warnings;
objects += loadWatcher->result().objects;
QmlJS::LibraryInfo libInfo = libraryInfo;
libInfo.setMetaObjects(objects);
libInfo.setModuleApis(typesWatcher->result().moduleApis);
libInfo.setDependencies(typesWatcher->result().dependencies);
libraryInfo.setMetaObjects(objects);
libraryInfo.setModuleApis(moduleApis);
libraryInfo.setDependencies(dependencies);
if (errors.isEmpty()) { if (errors.isEmpty()) {
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone); libInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone);
} else { } else {
printParseWarnings(libraryPath, errors.join(QLatin1Char('\n'))); printParseWarnings(libraryPath, errors.join(QLatin1Char('\n')));
errors.prepend(tr("Errors while reading typeinfo files:")); errors.prepend(tr("Errors while reading typeinfo files:"));
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errors.join(QLatin1Char('\n'))); libInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errors.join(QLatin1Char('\n')));
} }
if (!warnings.isEmpty()) if (!warnings.isEmpty())
printParseWarnings(libraryPath, warnings.join(QLatin1String("\n"))); printParseWarnings(libraryPath, warnings.join(QLatin1String("\n")));
libraryInfo.updateFingerprint(); libInfo.updateFingerprint();
m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); m_modelManager->updateLibraryInfo(libraryPath, libInfo);
});
if (!typesWatcher->result().dependencies.isEmpty()) {
loadWatcher->setFuture(loadDependencies(typesWatcher->result().dependencies, QSharedPointer<QSet<QString>>()));
}
});
typesWatcher->setFuture(loadQmlTypeDescription(qmltypesFilePaths));
} }
void PluginDumper::runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info, void PluginDumper::runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info,

View File

@@ -71,18 +71,30 @@ private:
QStringList typeInfoPaths; QStringList typeInfoPaths;
}; };
class QmlTypeDescription {
public:
QStringList errors;
QStringList warnings;
QList<LanguageUtils::FakeMetaObject::ConstPtr> objects;
QList<ModuleApiInfo> moduleApis;
QStringList dependencies;
};
class DependencyInfo {
public:
QStringList errors;
QStringList warnings;
QList<LanguageUtils::FakeMetaObject::ConstPtr> objects;
};
void runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info, const QStringList &arguments, const QString &importPath); void runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info, const QStringList &arguments, const QString &importPath);
void dump(const Plugin &plugin); void dump(const Plugin &plugin);
void loadQmlTypeDescription(const QStringList &path, QStringList &errors, QStringList &warnings, QFuture<QmlTypeDescription> loadQmlTypeDescription(const QStringList &path) const;
QList<LanguageUtils::FakeMetaObject::ConstPtr> &objects,
QList<ModuleApiInfo> *moduleApi,
QStringList *dependencies) const;
QString buildQmltypesPath(const QString &name) const; QString buildQmltypesPath(const QString &name) const;
void loadDependencies(const QStringList &dependencies,
QStringList &errors, QFuture<PluginDumper::DependencyInfo> loadDependencies(const QStringList &dependencies,
QStringList &warnings, QSharedPointer<QSet<QString>> visited) const;
QList<LanguageUtils::FakeMetaObject::ConstPtr> &objects,
QSet<QString> *visited = nullptr) const;
void loadQmltypesFile(const QStringList &qmltypesFilePaths, void loadQmltypesFile(const QStringList &qmltypesFilePaths,
const QString &libraryPath, const QString &libraryPath,
QmlJS::LibraryInfo libraryInfo); QmlJS::LibraryInfo libraryInfo);