From 7295bc96ccf9548d0f07051979f8395fd57a3fd4 Mon Sep 17 00:00:00 2001 From: Yasser Grimes Date: Wed, 12 Jul 2023 17:23:44 +0300 Subject: McuSupport: Make QmlProject c++ interfaces accecible to the CodeModel QtMCUs relies on the QmlProject file to list c++ interfaces that are available as components in Qml, roughly similar to qmlRegisterType. Prior to this commit there was no possible methode for QtCreator to know about thos c++ interfaces so whenever there are used "Uknown Components" error is shown. After building the project qmlprojectexporter through qmlinterfacegenerator generate Qml files for each .h interface in the "CMakeFiles/.dir/*.qml" or "...///*.qml" To make those documents available we make use of CustomImportProvider imports: for all importable qmlproject modules loadBuiltins (new): for documents that should be available implicitly for both the signatures is changed as all created documents require the snapshot object (insert functioin) to bound their lifetime to the snapshot as they are created as shared_ptrs. duo to passing snapshot as copies between threads, storing those documents locally will cause QtC to crash Task-number: UL-6167 Change-Id: I09781f6d4b62b203944bf30ab322502d25263b56 Reviewed-by: Alessandro Portale --- src/libs/qmljs/qmljsinterpreter.h | 13 +- src/libs/qmljs/qmljslink.cpp | 9 +- src/plugins/mcusupport/CMakeLists.txt | 1 + src/plugins/mcusupport/mcusupport.qbs | 2 + .../mcusupport/mcusupportimportprovider.cpp | 185 +++++++++++++++++++++ src/plugins/mcusupport/mcusupportimportprovider.h | 48 ++++++ src/plugins/mcusupport/mcusupportplugin.cpp | 2 + 7 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 src/plugins/mcusupport/mcusupportimportprovider.cpp create mode 100644 src/plugins/mcusupport/mcusupportimportprovider.h 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}; -- cgit v1.2.3