diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index 8574e37febe..8ca9f4ec26c 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -3,10 +3,11 @@ #pragma once +#include "qmljsdocument.h" +#include #include #include #include -#include #include @@ -1110,12 +1111,20 @@ class QMLJS_EXPORT CustomImportsProvider : public QObject { Q_OBJECT public: + typedef QHash> ImportsPerDocument; explicit CustomImportsProvider(QObject *parent = nullptr); virtual ~CustomImportsProvider(); static const QList allProviders(); - virtual QList imports(ValueOwner *valueOwner, const Document *context) const = 0; + virtual QList imports(ValueOwner *valueOwner, + const Document *context, + Snapshot *snapshot = nullptr) const = 0; + virtual void loadBuiltins([[maybe_unused]] ImportsPerDocument *importsPerDocument, + [[maybe_unused]] Imports *imports, + [[maybe_unused]] const Document *context, + [[maybe_unused]] ValueOwner *valueOwner, + [[maybe_unused]] Snapshot *snapshot) {} }; } // namespace QmlJS diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 7a34fbaa10d..b837037b7c8 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -209,9 +209,16 @@ Context::ImportsPerDocument LinkPrivate::linkImports() // Add custom imports for the opened document for (const auto &provider : CustomImportsProvider::allProviders()) { - const auto providerImports = provider->imports(m_valueOwner, document.data()); + const auto providerImports = provider->imports(m_valueOwner, + document.data(), + &m_snapshot); for (const auto &import : providerImports) importCache.insert(ImportCacheKey(import.info), import); + provider->loadBuiltins(&importsPerDocument, + imports, + document.data(), + m_valueOwner, + &m_snapshot); } populateImportedTypes(imports, document); diff --git a/src/plugins/mcusupport/CMakeLists.txt b/src/plugins/mcusupport/CMakeLists.txt index c5ce1bfa64c..f52c31b82ab 100644 --- a/src/plugins/mcusupport/CMakeLists.txt +++ b/src/plugins/mcusupport/CMakeLists.txt @@ -25,6 +25,7 @@ add_qtc_plugin(McuSupport mcuqmlprojectnode.cpp mcuqmlprojectnode.h mcubuildstep.cpp mcubuildstep.h dialogs/mcukitcreationdialog.cpp dialogs/mcukitcreationdialog.h + mcusupportimportprovider.cpp mcusupportimportprovider.h ) add_subdirectory(test) diff --git a/src/plugins/mcusupport/mcusupport.qbs b/src/plugins/mcusupport/mcusupport.qbs index 752b6a3e6ff..96fea109d75 100644 --- a/src/plugins/mcusupport/mcusupport.qbs +++ b/src/plugins/mcusupport/mcusupport.qbs @@ -60,6 +60,8 @@ QtcPlugin { "settingshandler.cpp", "dialogs/mcukitcreationdialog.h", "dialogs/mcukitcreationdialog.cpp", + "mcusupportimportprovider.h", + "mcusupportimportprovider.cpp", ] QtcTestFiles { diff --git a/src/plugins/mcusupport/mcusupportimportprovider.cpp b/src/plugins/mcusupport/mcusupportimportprovider.cpp new file mode 100644 index 00000000000..b78baf8bd94 --- /dev/null +++ b/src/plugins/mcusupport/mcusupportimportprovider.cpp @@ -0,0 +1,185 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mcusupportimportprovider.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +static const char uriPattern[]{R"(uri\s*:\s*[\'\"](\w+)[\'\"])"}; + +namespace McuSupport::Internal { +using namespace ProjectExplorer; + +// Get the MCU target build folder for a file +static std::optional getTargetBuildFolder(const FilePath &path) +{ + const Project *project = ProjectExplorer::ProjectManager::projectForFile(path); + if (!project) + return std::nullopt; + auto node = project->nodeForFilePath(path); + if (!node) + return std::nullopt; + + // Get the cmake target node (CMakeTargetNode is internal) + // Go up in hierarchy until the first CMake target node + const ProjectNode *targetNode = nullptr; + FilePath projectBuildFolder; + while (node) { + targetNode = node->asProjectNode(); + if (!targetNode) { + node = node->parentProjectNode(); + continue; + } + projectBuildFolder = FilePath::fromVariant( + targetNode->data(CMakeProjectManager::Constants::BUILD_FOLDER_ROLE)); + if (!projectBuildFolder.isDir()) { + node = node->parentProjectNode(); + targetNode = nullptr; + continue; + } + break; + } + + if (!targetNode) + return std::nullopt; + + return projectBuildFolder / "CMakeFiles" / (project->displayName() + ".dir"); +}; + +QList McuSupportImportProvider::imports(ValueOwner *valueOwner, + const Document *context, + Snapshot *snapshot) const +{ + QList ret; + + const FilePath path = context->fileName(); + const std::optional cmakeFilesPathOpt = getTargetBuildFolder(path); + if (!cmakeFilesPathOpt) + return {}; + + const FilePath inputFilePath = *cmakeFilesPathOpt / "config/input.json"; + + const QJsonDocument doc = QJsonDocument::fromJson(inputFilePath.fileContents().value_or("")); + if (!doc.isObject()) + return {}; + const QJsonObject mainProjectObj = doc.object(); + for (const QJsonValue &moduleValue : mainProjectObj["modulesDependencies"].toArray()) { + if (!moduleValue.isObject()) + continue; + const FilePath modulePath = FilePath::fromUserInput( + moduleValue.toObject()["qmlProjectFile"].toString()); + const QString fileContent = QString::fromLatin1(modulePath.fileContents().value_or("")); + QRegularExpressionMatch uriMatch = QRegularExpression(uriPattern).match(fileContent); + if (!uriMatch.hasMatch()) + continue; + QString moduleUri = uriMatch.captured(1); + const FilePath moduleFolder = *cmakeFilesPathOpt / moduleUri; + + Import import; + import.valid = true; + import.object = new ObjectValue(valueOwner, moduleUri); + import.info = ImportInfo::moduleImport(moduleUri, {1, 0}, QString()); + for (const auto &qmlFilePath : moduleFolder.dirEntries(FileFilter({"*.qml"}, QDir::Files))) { + Document::MutablePtr doc = Document::create(qmlFilePath, Dialect::Qml); + doc->setSource(QString::fromLatin1(qmlFilePath.fileContents().value_or(""))); + doc->parseQml(); + snapshot->insert(doc, true); + import.object->setMember(doc->componentName(), doc->bind()->rootObjectValue()); + } + ret << import; + } + return ret; +}; + +void McuSupportImportProvider::loadBuiltins(ImportsPerDocument *importsPerDocument, + Imports *imports, + const Document *context, + ValueOwner *valueOwner, + Snapshot *snapshot) +{ + Import import; + import.valid = true; + import.object = new ObjectValue(valueOwner, ""); + import.info = ImportInfo::moduleImport("qul", {1, 0}, QString()); + getInterfacesImport(context->fileName(), importsPerDocument, import, valueOwner, snapshot); + imports->append(import); +}; + +void McuSupportImportProvider::getInterfacesImport(const FilePath &path, + ImportsPerDocument *importsPerDocument, + Import &import, + ValueOwner *valueOwner, + Snapshot *snapshot) const +{ + const std::optional cmakeFilesPathOpt = getTargetBuildFolder(path); + if (!cmakeFilesPathOpt) + return; + + const FilePath inputFilePath = *cmakeFilesPathOpt / "config/input.json"; + + std::optional fileModule = getFileModule(path, inputFilePath); + FilePath lookupDir = *cmakeFilesPathOpt + / (fileModule && !fileModule->isEmpty() + ? QRegularExpression(uriPattern) + .match(QString::fromLatin1( + fileModule->fileContents().value_or(""))) + .captured(1) + : ""); + + for (const auto &qmlFilePath : lookupDir.dirEntries(FileFilter({"*.qml"}, QDir::Files))) { + Document::MutablePtr doc = Document::create(qmlFilePath, Dialect::Qml); + doc->setSource(QString::fromLatin1(qmlFilePath.fileContents().value_or(""))); + doc->parseQml(); + snapshot->insert(doc, true); + import.object->setMember(doc->componentName(), doc->bind()->rootObjectValue()); + importsPerDocument->insert(doc.data(), QSharedPointer(new Imports(valueOwner))); + } +} + +std::optional McuSupportImportProvider::getFileModule(const FilePath &file, + const FilePath &inputFile) const +{ + const auto doc = QJsonDocument::fromJson(inputFile.fileContents().value_or("")); + if (!doc.isObject()) + return {}; + + const QJsonObject mainProjectObject = doc.object(); + + // Mapping module objects starting with mainProject + const QJsonArray mainProjectQmlFiles = mainProjectObject["QmlFiles"].toArray(); + if (std::any_of(mainProjectQmlFiles.constBegin(), + mainProjectQmlFiles.constEnd(), + [&file](const QJsonValue &value) { + return FilePath::fromUserInput(value.toString()) == file; + })) + return std::nullopt; + + for (const QJsonValue &module : mainProjectObject["modulesDependencies"].toArray()) { + const QJsonObject moduleObject = module.toObject(); + const QJsonArray moduleQmlFiles = moduleObject["QmlFiles"].toArray(); + if (std::any_of(moduleQmlFiles.constBegin(), + moduleQmlFiles.constEnd(), + [&file](const QJsonValue &value) { + return FilePath::fromUserInput(value.toString()) == file; + })) + return FilePath::fromUserInput(moduleObject["qmlProjectFile"].toString()); + } + return std::nullopt; +} +}; // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/mcusupportimportprovider.h b/src/plugins/mcusupport/mcusupportimportprovider.h new file mode 100644 index 00000000000..829fa711505 --- /dev/null +++ b/src/plugins/mcusupport/mcusupportimportprovider.h @@ -0,0 +1,48 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "mcusupport_global.h" +#include "mcusupportplugin.h" + +#include +#include +#include + +namespace McuSupport::Internal { +using namespace QmlJS; +using namespace Utils; +class McuSupportImportProvider : public CustomImportsProvider +{ +public: + McuSupportImportProvider() {} + ~McuSupportImportProvider() {} + + // Overridden functions + virtual QList imports(ValueOwner *valueOwner, + const Document *context, + Snapshot *snapshot) const override; + virtual void loadBuiltins(ImportsPerDocument *importsPerDocument, + Imports *imports, + const Document *context, + ValueOwner *valueOwner, + Snapshot *snapshot) override; + + // Add to the interfaces needed for a document + // path: opened qml document + // importsPerDocument: imports available in the document (considered imported) + // import: qul import containing builtin types (interfaces) + // valueOwner: imports members owner + // snapshot: where qul documenents live + void getInterfacesImport(const FilePath &path, + ImportsPerDocument *importsPerDocument, + Import &import, + ValueOwner *valueOwner, + Snapshot *snapshot) const; + + // Get the qmlproject module which a qmlfile is part of + // nullopt means the file is part of the main project + std::optional getFileModule(const FilePath &file, const FilePath &inputFile) const; +}; +}; // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/mcusupportplugin.cpp b/src/plugins/mcusupport/mcusupportplugin.cpp index f531b93a2aa..a9f8ff0d499 100644 --- a/src/plugins/mcusupport/mcusupportplugin.cpp +++ b/src/plugins/mcusupport/mcusupportplugin.cpp @@ -8,6 +8,7 @@ #include "mcuqmlprojectnode.h" #include "mcusupportconstants.h" #include "mcusupportdevice.h" +#include "mcusupportimportprovider.h" #include "mcusupportoptions.h" #include "mcusupportoptionspage.h" #include "mcusupportrunconfiguration.h" @@ -102,6 +103,7 @@ public: McuSupportOptions m_options{m_settingsHandler}; McuSupportOptionsPage optionsPage{m_options, m_settingsHandler}; MCUBuildStepFactory mcuBuildStepFactory; + McuSupportImportProvider mcuImportProvider; }; // class McuSupportPluginPrivate static McuSupportPluginPrivate *dd{nullptr};