forked from qt-creator/qt-creator
On session switch we cancel all the running tasks. We connect to the SessionManager::aboutToLoadSession() signal, as this one is emitted just before loading new session's projects. We don't connect to SessionManager::aboutToUnloadSession(), since after this signal is emitted the unloading may be canceled due to e.g. showing the dialog asking for saving changed files in the unloaded session (the user may cancel the process of unloading). In contrast to what we do on shutdown, we don't wait for futures being finished here - it's just enough we cancel all of them. Fixes: QTCREATORBUG-25583 Change-Id: I01eeca00d150f6e98a80a050c6a19efb848b9954 Reviewed-by: hjk <hjk@qt.io>
349 lines
14 KiB
C++
349 lines
14 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "qmljsmodelmanager.h"
|
|
#include "qmljssemanticinfo.h"
|
|
#include "qmljsbundleprovider.h"
|
|
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
|
#include <coreplugin/editormanager/ieditor.h>
|
|
#include <coreplugin/messagemanager.h>
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
|
#include <coreplugin/session.h>
|
|
|
|
#include <cppeditor/cppmodelmanager.h>
|
|
|
|
#include <projectexplorer/buildconfiguration.h>
|
|
#include <projectexplorer/buildsystem.h>
|
|
#include <projectexplorer/project.h>
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
|
#include <projectexplorer/projectmanager.h>
|
|
#include <projectexplorer/projectnodes.h>
|
|
#include <projectexplorer/projecttree.h>
|
|
#include <projectexplorer/runconfiguration.h>
|
|
#include <projectexplorer/projectmanager.h>
|
|
#include <projectexplorer/target.h>
|
|
|
|
#include <qmljs/qmljsbind.h>
|
|
#include <qmljs/qmljsfindexportedcpptypes.h>
|
|
#include <qmljs/qmljsplugindumper.h>
|
|
|
|
#include <qtsupport/qtkitaspect.h>
|
|
#include <qtsupport/qtsupportconstants.h>
|
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/hostosinfo.h>
|
|
#include <utils/mimeconstants.h>
|
|
#include <utils/mimeutils.h>
|
|
|
|
#include <QLibraryInfo>
|
|
#include <QTextDocument>
|
|
#include <QTextStream>
|
|
#include <QTimer>
|
|
#include <QSet>
|
|
|
|
using namespace Utils;
|
|
using namespace Core;
|
|
using namespace ProjectExplorer;
|
|
using namespace QmlJS;
|
|
|
|
namespace QmlJSTools {
|
|
namespace Internal {
|
|
|
|
static void setupProjectInfoQmlBundles(ModelManagerInterface::ProjectInfo &projectInfo)
|
|
{
|
|
Target *activeTarget = nullptr;
|
|
if (projectInfo.project)
|
|
activeTarget = projectInfo.project->activeTarget();
|
|
Kit *activeKit = activeTarget ? activeTarget->kit() : KitManager::defaultKit();
|
|
const QHash<QString, QString> replacements = {{QLatin1String("$(QT_INSTALL_QML)"), projectInfo.qtQmlPath.toString()}};
|
|
|
|
for (IBundleProvider *bp : IBundleProvider::allBundleProviders())
|
|
bp->mergeBundlesForKit(activeKit, projectInfo.activeBundle, replacements);
|
|
|
|
projectInfo.extendedBundle = projectInfo.activeBundle;
|
|
|
|
if (projectInfo.project) {
|
|
QSet<Kit *> currentKits;
|
|
const QList<Target *> targets = projectInfo.project->targets();
|
|
for (const Target *t : targets)
|
|
currentKits.insert(t->kit());
|
|
currentKits.remove(activeKit);
|
|
for (Kit *kit : std::as_const(currentKits)) {
|
|
for (IBundleProvider *bp : IBundleProvider::allBundleProviders())
|
|
bp->mergeBundlesForKit(kit, projectInfo.extendedBundle, replacements);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void findAllQrcFiles(const FilePath &filePath, FilePaths &out)
|
|
{
|
|
filePath.iterateDirectory(
|
|
[&out](const FilePath &path) {
|
|
out.append(path.canonicalPath());
|
|
return IterationPolicy::Continue;
|
|
},
|
|
{{"*.qrc"}, QDir::Files});
|
|
}
|
|
|
|
static FilePaths findGeneratedQrcFiles(const ModelManagerInterface::ProjectInfo &pInfo,
|
|
const FilePaths &hiddenRccFolders)
|
|
{
|
|
FilePaths result;
|
|
// Search in Application Directories for directories named ".rcc"
|
|
// and add all .qrc files in there to the resource file list.
|
|
for (const Utils::FilePath &path : pInfo.applicationDirectories) {
|
|
Utils::FilePath generatedQrcDir = path.pathAppended(".rcc");
|
|
findAllQrcFiles(generatedQrcDir, result);
|
|
}
|
|
|
|
for (const Utils::FilePath &hiddenRccFolder : hiddenRccFolders) {
|
|
findAllQrcFiles(hiddenRccFolder, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ModelManagerInterface::ProjectInfo ModelManager::defaultProjectInfoForProject(
|
|
Project *project, const FilePaths &hiddenRccFolders) const
|
|
{
|
|
ModelManagerInterface::ProjectInfo projectInfo;
|
|
projectInfo.project = project;
|
|
projectInfo.qmlDumpEnvironment = Utils::Environment::systemEnvironment();
|
|
Target *activeTarget = nullptr;
|
|
if (project) {
|
|
using namespace Utils::Constants;
|
|
const QSet<QString> qmlTypeNames = { QML_MIMETYPE ,
|
|
QBS_MIMETYPE,
|
|
QMLPROJECT_MIMETYPE,
|
|
QMLTYPES_MIMETYPE,
|
|
QMLUI_MIMETYPE };
|
|
projectInfo.sourceFiles = project->files([&qmlTypeNames](const Node *n) {
|
|
if (!Project::SourceFiles(n))
|
|
return false;
|
|
const FileNode *fn = n->asFileNode();
|
|
return fn && fn->fileType() == FileType::QML
|
|
&& qmlTypeNames.contains(Utils::mimeTypeForFile(fn->filePath(),
|
|
MimeMatchMode::MatchExtension).name());
|
|
});
|
|
activeTarget = project->activeTarget();
|
|
}
|
|
Kit *activeKit = activeTarget ? activeTarget->kit() : KitManager::defaultKit();
|
|
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(activeKit);
|
|
|
|
projectInfo.tryQmlDump = false;
|
|
|
|
if (activeTarget) {
|
|
FilePath baseDir;
|
|
auto addAppDir = [&baseDir, &projectInfo](const FilePath &mdir) {
|
|
auto dir = mdir.cleanPath();
|
|
if (!baseDir.path().isEmpty()) {
|
|
auto rDir = dir.relativePathFrom(baseDir);
|
|
// do not add directories outside the build directory
|
|
// this might happen for example when we think an executable path belongs to
|
|
// a bundle, and we need to remove extra directories, but that was not the case
|
|
if (rDir.path().split(u'/').contains(QStringLiteral(u"..")))
|
|
return;
|
|
}
|
|
if (!projectInfo.applicationDirectories.contains(dir))
|
|
projectInfo.applicationDirectories.append(dir);
|
|
};
|
|
|
|
if (BuildConfiguration *bc = activeTarget->activeBuildConfiguration()) {
|
|
// Append QML2_IMPORT_PATH if it is defined in build configuration.
|
|
// It enables qmlplugindump to correctly dump custom plugins or other dependent
|
|
// plugins that are not installed in default Qt qml installation directory.
|
|
projectInfo.qmlDumpEnvironment.appendOrSet("QML2_IMPORT_PATH",
|
|
bc->environment().expandedValueForKey(
|
|
"QML2_IMPORT_PATH"));
|
|
// Treat every target (library or application) in the build directory
|
|
|
|
FilePath dir = bc->buildDirectory();
|
|
baseDir = dir.absoluteFilePath();
|
|
addAppDir(dir);
|
|
}
|
|
// Qml loads modules from the following sources
|
|
// 1. The build directory of the executable
|
|
// 2. Any QML_IMPORT_PATH (environment variable) or IMPORT_PATH (parameter to qt_add_qml_module)
|
|
// 3. The Qt import path
|
|
// For an IDE things are a bit more complicated because source files might be edited,
|
|
// and the directory of the executable might be outdated.
|
|
// Here we try to get the directory of the executable, adding all targets
|
|
auto *bs = activeTarget->buildSystem();
|
|
const auto appTargets = bs ? bs->applicationTargets() : QList<BuildTargetInfo>{};
|
|
for (const auto &target : appTargets) {
|
|
if (target.targetFilePath.isEmpty())
|
|
continue;
|
|
auto dir = target.targetFilePath.parentDir();
|
|
projectInfo.applicationDirectories.append(dir);
|
|
// unfortunately the build directory of the executable where cmake puts the qml
|
|
// might be different than the directory of the executable:
|
|
if (HostOsInfo::isWindowsHost()) {
|
|
// On Windows systems QML type information is located one directory higher as we build
|
|
// in dedicated "debug" and "release" directories
|
|
addAppDir(dir.parentDir());
|
|
} else if (HostOsInfo::isMacHost()) {
|
|
// On macOS and iOS when building a bundle this is not the case and
|
|
// we have to go up up to three additional directories
|
|
// (BundleName.app/Contents/MacOS or BundleName.app/Contents for iOS)
|
|
if (dir.fileName() == u"MacOS")
|
|
dir = dir.parentDir();
|
|
if (dir.fileName() == u"Contents")
|
|
dir = dir.parentDir().parentDir();
|
|
addAppDir(dir);
|
|
}
|
|
}
|
|
}
|
|
if (qtVersion && qtVersion->isValid()) {
|
|
projectInfo.tryQmlDump = project && qtVersion->type() == QLatin1String(QtSupport::Constants::DESKTOPQT);
|
|
projectInfo.qtQmlPath = qtVersion->qmlPath();
|
|
auto v = qtVersion->qtVersion();
|
|
projectInfo.qmllsPath = ModelManagerInterface::qmllsForBinPath(qtVersion->hostBinPath(), v);
|
|
projectInfo.qtVersionString = qtVersion->qtVersionString();
|
|
} else if (!activeKit || !activeKit->value(QtSupport::Constants::FLAGS_SUPPLIES_QTQUICK_IMPORT_PATH, false).toBool()) {
|
|
projectInfo.qtQmlPath = FilePath::fromUserInput(QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath));
|
|
projectInfo.qmllsPath = ModelManagerInterface::qmllsForBinPath(
|
|
FilePath::fromUserInput(QLibraryInfo::path(QLibraryInfo::BinariesPath)), QLibraryInfo::version());
|
|
projectInfo.qtVersionString = QLatin1String(qVersion());
|
|
}
|
|
|
|
projectInfo.qmlDumpPath.clear();
|
|
const QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(activeKit);
|
|
if (version && projectInfo.tryQmlDump) {
|
|
projectInfo.qmlDumpPath = version->qmlplugindumpFilePath();
|
|
projectInfo.qmlDumpHasRelocatableFlag = version->hasQmlDumpWithRelocatableFlag();
|
|
}
|
|
|
|
setupProjectInfoQmlBundles(projectInfo);
|
|
projectInfo.generatedQrcFiles = findGeneratedQrcFiles(projectInfo, hiddenRccFolders);
|
|
return projectInfo;
|
|
}
|
|
|
|
QHash<QString,Dialect> ModelManager::initLanguageForSuffix() const
|
|
{
|
|
QHash<QString,Dialect> res = ModelManagerInterface::languageForSuffix();
|
|
|
|
if (ICore::instance()) {
|
|
using namespace Utils::Constants;;
|
|
MimeType jsSourceTy = Utils::mimeTypeForName(JS_MIMETYPE);
|
|
const QStringList jsSuffixes = jsSourceTy.suffixes();
|
|
for (const QString &suffix : jsSuffixes)
|
|
res[suffix] = Dialect::JavaScript;
|
|
MimeType qmlSourceTy = Utils::mimeTypeForName(QML_MIMETYPE);
|
|
const QStringList qmlSuffixes = qmlSourceTy.suffixes();
|
|
for (const QString &suffix : qmlSuffixes)
|
|
res[suffix] = Dialect::Qml;
|
|
MimeType qbsSourceTy = Utils::mimeTypeForName(QBS_MIMETYPE);
|
|
const QStringList qbsSuffixes = qbsSourceTy.suffixes();
|
|
for (const QString &suffix : qbsSuffixes)
|
|
res[suffix] = Dialect::QmlQbs;
|
|
MimeType qmlProjectSourceTy = Utils::mimeTypeForName(QMLPROJECT_MIMETYPE);
|
|
const QStringList qmlProjSuffixes = qmlProjectSourceTy.suffixes();
|
|
for (const QString &suffix : qmlProjSuffixes)
|
|
res[suffix] = Dialect::QmlProject;
|
|
MimeType qmlUiSourceTy = Utils::mimeTypeForName(QMLUI_MIMETYPE);
|
|
const QStringList qmlUiSuffixes = qmlUiSourceTy.suffixes();
|
|
for (const QString &suffix : qmlUiSuffixes)
|
|
res[suffix] = Dialect::QmlQtQuick2Ui;
|
|
MimeType jsonSourceTy = Utils::mimeTypeForName(JSON_MIMETYPE);
|
|
const QStringList jsonSuffixes = jsonSourceTy.suffixes();
|
|
for (const QString &suffix : jsonSuffixes)
|
|
res[suffix] = Dialect::Json;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
QHash<QString,Dialect> ModelManager::languageForSuffix() const
|
|
{
|
|
static QHash<QString,Dialect> res = initLanguageForSuffix();
|
|
return res;
|
|
}
|
|
|
|
ModelManager::ModelManager()
|
|
{
|
|
qRegisterMetaType<QmlJSTools::SemanticInfo>("QmlJSTools::SemanticInfo");
|
|
CppQmlTypesLoader::defaultObjectsInitializer = [this] { loadDefaultQmlTypeDescriptions(); };
|
|
}
|
|
|
|
ModelManager::~ModelManager() = default;
|
|
|
|
void ModelManager::delayedInitialization()
|
|
{
|
|
CppEditor::CppModelManager *cppModelManager = CppEditor::CppModelManager::instance();
|
|
// It's important to have a direct connection here so we can prevent
|
|
// the source and AST of the cpp document being cleaned away.
|
|
connect(cppModelManager, &CppEditor::CppModelManager::documentUpdated,
|
|
this, &ModelManagerInterface::maybeQueueCppQmlTypeUpdate, Qt::DirectConnection);
|
|
|
|
connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
|
|
this, &ModelManager::removeProjectInfo);
|
|
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
|
|
this, &ModelManager::updateDefaultProjectInfo);
|
|
connect(SessionManager::instance(), &SessionManager::aboutToLoadSession,
|
|
this, &ModelManager::cancelAllThreads);
|
|
|
|
ViewerContext qbsVContext;
|
|
qbsVContext.language = Dialect::QmlQbs;
|
|
qbsVContext.paths.insert(ICore::resourcePath("qbs"));
|
|
setDefaultVContext(qbsVContext);
|
|
}
|
|
|
|
void ModelManager::loadDefaultQmlTypeDescriptions()
|
|
{
|
|
if (ICore::instance()) {
|
|
loadQmlTypeDescriptionsInternal(ICore::resourcePath().toString());
|
|
loadQmlTypeDescriptionsInternal(ICore::userResourcePath().toString());
|
|
}
|
|
}
|
|
|
|
void ModelManager::writeMessageInternal(const QString &msg) const
|
|
{
|
|
MessageManager::writeFlashing(msg);
|
|
}
|
|
|
|
ModelManagerInterface::WorkingCopy ModelManager::workingCopyInternal() const
|
|
{
|
|
WorkingCopy workingCopy;
|
|
|
|
if (!Core::ICore::instance())
|
|
return workingCopy;
|
|
|
|
const QList<IDocument *> documents = DocumentModel::openedDocuments();
|
|
for (IDocument *document : documents) {
|
|
const Utils::FilePath key = document->filePath();
|
|
if (auto textDocument = qobject_cast<const TextEditor::TextDocument *>(document)) {
|
|
// TODO the language should be a property on the document, not the editor
|
|
if (DocumentModel::editorsForDocument(document).constFirst()
|
|
->context().contains(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID)) {
|
|
workingCopy.insert(key, textDocument->plainText(),
|
|
textDocument->document()->revision());
|
|
}
|
|
}
|
|
}
|
|
|
|
return workingCopy;
|
|
}
|
|
|
|
void ModelManager::updateDefaultProjectInfo()
|
|
{
|
|
// needs to be performed in the ui thread
|
|
Project *currentProject = ProjectManager::startupProject();
|
|
setDefaultProject(containsProject(currentProject)
|
|
? projectInfo(currentProject)
|
|
: defaultProjectInfoForProject(currentProject, {}),
|
|
currentProject);
|
|
}
|
|
|
|
|
|
void ModelManager::addTaskInternal(const QFuture<void> &result, const QString &msg,
|
|
const char *taskId) const
|
|
{
|
|
ProgressManager::addTask(result, msg, taskId);
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace QmlJSTools
|