forked from qt-creator/qt-creator
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:
@@ -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,60 @@ void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
|
||||
if (!privatePlugin)
|
||||
ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages));
|
||||
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages));
|
||||
}
|
||||
|
||||
const QByteArray output = process->readAllStandardOutput();
|
||||
|
||||
class CppQmlTypesInfo {
|
||||
public:
|
||||
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));
|
||||
};
|
||||
|
||||
auto watcher = QSharedPointer<QFutureWatcher<CppQmlTypesInfo>>(new QFutureWatcher<CppQmlTypesInfo>());
|
||||
|
||||
connect(watcher.data(), &QFutureWatcher<CppQmlTypesInfo>::finished, this,
|
||||
[this, watcher, libraryInfo, privatePlugin, libraryPath] {
|
||||
CppQmlTypesInfo infos = watcher->result();
|
||||
|
||||
LibraryInfo libInfo = libraryInfo;
|
||||
|
||||
if (!infos.error.isEmpty()) {
|
||||
libInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError,
|
||||
qmldumpErrorMessage(libraryPath, infos.error));
|
||||
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 {
|
||||
libraryInfo.setMetaObjects(objectsList.values());
|
||||
libraryInfo.setModuleApis(moduleApis);
|
||||
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone);
|
||||
}
|
||||
|
||||
if (!warning.isEmpty())
|
||||
printParseWarnings(libraryPath, warning);
|
||||
}
|
||||
libraryInfo.updateFingerprint();
|
||||
|
||||
m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError)
|
||||
@@ -344,16 +370,15 @@ 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 {
|
||||
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)) {
|
||||
errors += reader.errorString();
|
||||
result.errors += reader.errorString();
|
||||
continue;
|
||||
}
|
||||
QString error;
|
||||
@@ -364,18 +389,23 @@ void PluginDumper::loadQmlTypeDescription(const QStringList &paths,
|
||||
CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &objs, &apis, &deps,
|
||||
&error, &warning, p);
|
||||
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 {
|
||||
objects += objs.values();
|
||||
if (moduleApi)
|
||||
*moduleApi += apis;
|
||||
result.objects += objs.values();
|
||||
result.moduleApis += apis;
|
||||
if (!deps.isEmpty())
|
||||
*dependencies += deps;
|
||||
result.dependencies += deps;
|
||||
}
|
||||
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.
|
||||
* \param name
|
||||
@@ -421,16 +451,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 +466,84 @@ 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());
|
||||
|
||||
auto typesWatcher = QSharedPointer<QFutureWatcher<PluginDumper::QmlTypeDescription>>(new QFutureWatcher<PluginDumper::QmlTypeDescription>());
|
||||
connect(typesWatcher.data(), &QFutureWatcher<PluginDumper::QmlTypeDescription>::finished, this, [this, iface, visited, typesWatcher] {
|
||||
QStringList newDependencies = typesWatcher->result().dependencies;
|
||||
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,
|
||||
const QString &libraryPath,
|
||||
QmlJS::LibraryInfo libraryInfo)
|
||||
{
|
||||
QStringList errors;
|
||||
QStringList warnings;
|
||||
QList<FakeMetaObject::ConstPtr> objects;
|
||||
QList<ModuleApiInfo> moduleApis;
|
||||
QStringList dependencies;
|
||||
auto typesWatcher = QSharedPointer<QFutureWatcher<PluginDumper::QmlTypeDescription>>(new QFutureWatcher<PluginDumper::QmlTypeDescription>());
|
||||
connect(typesWatcher.data(), &QFutureWatcher<PluginDumper::QmlTypeDescription>::finished, this, [this, typesWatcher, libraryPath, libraryInfo] {
|
||||
|
||||
loadQmlTypeDescription(qmltypesFilePaths, errors, warnings, objects, &moduleApis, &dependencies);
|
||||
loadDependencies(dependencies, errors, warnings, objects);
|
||||
auto loadWatcher = QSharedPointer<QFutureWatcher<PluginDumper::DependencyInfo>>(new QFutureWatcher<PluginDumper::DependencyInfo>());
|
||||
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()) {
|
||||
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone);
|
||||
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')));
|
||||
libInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errors.join(QLatin1Char('\n')));
|
||||
}
|
||||
|
||||
if (!warnings.isEmpty())
|
||||
printParseWarnings(libraryPath, warnings.join(QLatin1String("\n")));
|
||||
|
||||
libraryInfo.updateFingerprint();
|
||||
m_modelManager->updateLibraryInfo(libraryPath, libraryInfo);
|
||||
libInfo.updateFingerprint();
|
||||
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,
|
||||
|
@@ -71,18 +71,30 @@ private:
|
||||
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 dump(const Plugin &plugin);
|
||||
void loadQmlTypeDescription(const QStringList &path, QStringList &errors, QStringList &warnings,
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> &objects,
|
||||
QList<ModuleApiInfo> *moduleApi,
|
||||
QStringList *dependencies) const;
|
||||
QFuture<QmlTypeDescription> loadQmlTypeDescription(const QStringList &path) const;
|
||||
QString buildQmltypesPath(const QString &name) const;
|
||||
void loadDependencies(const QStringList &dependencies,
|
||||
QStringList &errors,
|
||||
QStringList &warnings,
|
||||
QList<LanguageUtils::FakeMetaObject::ConstPtr> &objects,
|
||||
QSet<QString> *visited = nullptr) const;
|
||||
|
||||
QFuture<PluginDumper::DependencyInfo> loadDependencies(const QStringList &dependencies,
|
||||
QSharedPointer<QSet<QString>> visited) const;
|
||||
|
||||
void loadQmltypesFile(const QStringList &qmltypesFilePaths,
|
||||
const QString &libraryPath,
|
||||
QmlJS::LibraryInfo libraryInfo);
|
||||
|
Reference in New Issue
Block a user