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/<target>.dir/*.qml" or ".../<target.dir>/<module>/*.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 <alessandro.portale@qt.io>
This commit is contained in:
Yasser Grimes
2023-07-12 17:23:44 +03:00
parent 0b08a9752a
commit 7295bc96cc
7 changed files with 257 additions and 3 deletions

View File

@@ -3,10 +3,11 @@
#pragma once
#include "qmljsdocument.h"
#include <qmljs/parser/qmljsastfwd_p.h>
#include <qmljs/qmljs_global.h>
#include <qmljs/qmljsconstants.h>
#include <qmljs/qmljsimportdependencies.h>
#include <qmljs/parser/qmljsastfwd_p.h>
#include <languageutils/fakemetaobject.h>
@@ -1110,12 +1111,20 @@ class QMLJS_EXPORT CustomImportsProvider : public QObject
{
Q_OBJECT
public:
typedef QHash<const Document *, QSharedPointer<const Imports>> ImportsPerDocument;
explicit CustomImportsProvider(QObject *parent = nullptr);
virtual ~CustomImportsProvider();
static const QList<CustomImportsProvider *> allProviders();
virtual QList<Import> imports(ValueOwner *valueOwner, const Document *context) const = 0;
virtual QList<Import> 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

View File

@@ -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);

View File

@@ -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)

View File

@@ -60,6 +60,8 @@ QtcPlugin {
"settingshandler.cpp",
"dialogs/mcukitcreationdialog.h",
"dialogs/mcukitcreationdialog.cpp",
"mcusupportimportprovider.h",
"mcusupportimportprovider.cpp",
]
QtcTestFiles {

View File

@@ -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 <cmakeprojectmanager/cmakeprojectconstants.h>
#include <languageutils/componentversion.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <qmljs/qmljsbind.h>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsvalueowner.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QRegularExpression>
#include <optional>
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<FilePath> 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<Import> McuSupportImportProvider::imports(ValueOwner *valueOwner,
const Document *context,
Snapshot *snapshot) const
{
QList<Import> ret;
const FilePath path = context->fileName();
const std::optional<FilePath> 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, "<qul>");
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<FilePath> cmakeFilesPathOpt = getTargetBuildFolder(path);
if (!cmakeFilesPathOpt)
return;
const FilePath inputFilePath = *cmakeFilesPathOpt / "config/input.json";
std::optional<FilePath> 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<Imports>(new Imports(valueOwner)));
}
}
std::optional<FilePath> 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

View File

@@ -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 <utils/filepath.h>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsinterpreter.h>
namespace McuSupport::Internal {
using namespace QmlJS;
using namespace Utils;
class McuSupportImportProvider : public CustomImportsProvider
{
public:
McuSupportImportProvider() {}
~McuSupportImportProvider() {}
// Overridden functions
virtual QList<Import> 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<FilePath> getFileModule(const FilePath &file, const FilePath &inputFile) const;
};
}; // namespace McuSupport::Internal

View File

@@ -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};