// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "mcumoduleprojectitem.h" #include #include #include #include using namespace Qt::Literals::StringLiterals; namespace Constants { namespace QmlDir { constexpr auto QMLDIR = "qmldir"_L1; constexpr auto MODULE = "module"_L1; constexpr auto QML_FILE_FILTER = "*.qml"_L1; } // namespace QmlDir namespace Json { constexpr auto MODULE_URI = "moduleUri"_L1; constexpr auto QML_FILES = "qmlFiles"_L1; constexpr auto QMLPROJECT_PATH = "qmlProjectPath"_L1; } // namespace Json namespace QmlProject { constexpr auto QMLPROJECT_EXTENSION = ".qmlproject"_L1; constexpr auto MCU_MODULE = "MCU.Module"_L1; constexpr auto URI = "uri"_L1; constexpr auto QML_FILES = "QmlFiles"_L1; constexpr auto FILES = "files"_L1; const auto QMLPROJECT_TEMPLATE = QString(R"(/* File generated by Qt Design Studio */ import QmlProject 1.3 Project { MCU.Module { uri: %1 } QmlFiles { files: [ %2 ] } } )"); } // namespace QmlProject } // namespace Constants namespace { Q_LOGGING_CATEGORY(log, "QmlProjectManager.McuModuleProjectItem", QtCriticalMsg) bool isValidQmlProjectPath(const Utils::FilePath &path) { return path.endsWith(Constants::QmlProject::QMLPROJECT_EXTENSION) && (path.ensureExistingFile() || path.parentDir().isWritableDir()); } QJsonObject parseQmlProjectFile(const Utils::FilePath &qmlproject) { auto qmlprojectPathStr = qmlproject.toFSPathString(); if (!qmlproject.exists()) { qCWarning(log) << "qmlproject file not found:" << qmlprojectPathStr; return {}; } QmlJS::SimpleReader reader; QmlJS::SimpleReaderNode::Ptr rootNode = reader.readFile(qmlprojectPathStr); if (!reader.errors().isEmpty() || !rootNode->isValid()) { qCWarning(log) << "Unable to parse:" << qmlprojectPathStr; qCWarning(log) << reader.errors(); return {}; } QJsonObject result; result.insert(Constants::Json::QMLPROJECT_PATH, qmlprojectPathStr); auto checkNodeName = [](const QString &node, const QString &expecedName) { return node.compare(expecedName, Qt::CaseInsensitive) == 0; }; //expected just two nodes: MCU.Module and QmlFiles for (const auto &childNode : rootNode->children()) { auto nodeName = childNode->name(); if (checkNodeName(nodeName, Constants::QmlProject::MCU_MODULE)) { result.insert(Constants::Json::MODULE_URI, childNode->property(Constants::QmlProject::URI).value.toString()); } else if (checkNodeName(nodeName, Constants::QmlProject::QML_FILES)) { result.insert(Constants::Json::QML_FILES, childNode->property(Constants::QmlProject::FILES).value.toJsonArray()); } else { qCWarning(log) << "Unsupported node:" << nodeName; } } return result; } } // namespace namespace QmlProjectManager { McuModuleProjectItem::McuModuleProjectItem(const QJsonObject &project) : m_project(project) {} McuModuleProjectItem::McuModuleProjectItem(const Utils::FilePath &qmlprojectFile) : m_qmlProjectFile(qmlprojectFile) , m_project(parseQmlProjectFile(m_qmlProjectFile)) { } std::optional McuModuleProjectItem::fromQmldirModule(const Utils::FilePath &qmldirFile) { auto qmldirFileStr = qmldirFile.toFSPathString(); // check qmldirFile if (!qmldirFile.exists()) { qCWarning(log) << "File not found:" << qmldirFileStr; return {}; } if (qmldirFile.fileName() != Constants::QmlDir::QMLDIR) { qCWarning(log) << "It's not qmldir file:" << qmldirFileStr; return {}; } auto qmldirContents = qmldirFile.fileContents(); if (!qmldirContents) { qCWarning(log) << "Unable to read the file:" << qmldirFileStr << ", error:" << qmldirContents.error(); return {}; } // find module name QByteArray fileContents = *qmldirContents; QTextStream ts(fileContents); QString moduleName; while (!ts.atEnd()) { QString line = ts.readLine().trimmed(); if (line.startsWith(Constants::QmlDir::MODULE, Qt::CaseInsensitive)) { auto list = line.split(' '); if (list.size() != 2) { qCWarning(log) << "Invalid module identifier:" << line; return {}; } moduleName = list.last(); break; } } if (moduleName.isEmpty()) { qCWarning(log) << "Module name not found in the qmldir"; return {}; } // list qml files const auto qmldirParent = qmldirFile.parentDir(); auto qmlDirEntries = qmldirParent.dirEntries(Utils::FileFilter{{Constants::QmlDir::QML_FILE_FILTER}, QDir::NoFilter, QDirIterator::Subdirectories}); if (qmlDirEntries.empty()) { qCWarning(log) << "No qml files found in:" << qmldirParent; return {}; } auto qmlFiles = Utils::transform(qmlDirEntries, [qmldirParent](const Utils::FilePath &path) { return path.relativePathFromDir(qmldirParent); }); // build mcu module project QJsonObject result; result.insert(Constants::Json::MODULE_URI, moduleName); result.insert(Constants::Json::QML_FILES, QJsonArray::fromStringList(qmlFiles)); auto filename = moduleName.replace('.', '_'); auto qmlprojectPath = qmldirParent.resolvePath( Utils::FilePath::fromString(filename + Constants::QmlProject::QMLPROJECT_EXTENSION)); result.insert(Constants::Json::QMLPROJECT_PATH, qmlprojectPath.toFSPathString()); return McuModuleProjectItem(result); } bool McuModuleProjectItem::isValid() const noexcept { return !uri().isEmpty() && !qmlFiles().isEmpty() && isValidQmlProjectPath(qmlProjectPath()); } QString McuModuleProjectItem::uri() const noexcept { return m_project[Constants::Json::MODULE_URI].toString(); } void McuModuleProjectItem::setUri(const QString &moduleUri) { m_project[Constants::Json::MODULE_URI] = moduleUri; } QStringList McuModuleProjectItem::qmlFiles() const noexcept { return m_project[Constants::Json::QML_FILES].toVariant().toStringList(); } void McuModuleProjectItem::setQmlFiles(const QStringList &files) { m_project[Constants::Json::QML_FILES] = QJsonArray::fromStringList(files); } Utils::FilePath McuModuleProjectItem::qmlProjectPath() const noexcept { return Utils::FilePath::fromString(m_project[Constants::Json::QMLPROJECT_PATH].toString()); } void McuModuleProjectItem::setQmlProjectPath(const Utils::FilePath &path) { m_project[Constants::Json::QMLPROJECT_PATH] = path.toFSPathString(); } QJsonObject McuModuleProjectItem::project() const noexcept { return m_project; } bool McuModuleProjectItem::saveQmlProjectFile() const { if (!isValid()) { return false; } auto path = qmlProjectPath(); if (path.exists()) { if (McuModuleProjectItem old(path); old == *this) { return false; } } QTC_ASSERT_RESULT(path.writeFileContents(jsonToQmlproject()), return false); return true; } bool McuModuleProjectItem::operator==(const McuModuleProjectItem &other) const noexcept { return this->project() == other.project(); } QByteArray McuModuleProjectItem::jsonToQmlproject() const { auto quoted = [](const QString &s) { return QString("\"%1\"").arg(s); }; auto indent = [](int tabs = 1) { return QString(" ").repeated(tabs * 4); }; auto quotedQmlFiles = Utils::transform(qmlFiles(), [quoted](const QString &file) { return quoted(file); }); QString qmlFilesSeparator; QTextStream ts(&qmlFilesSeparator); ts << "," << Qt::endl << indent(3); return Constants::QmlProject::QMLPROJECT_TEMPLATE .arg(quoted(uri()), quotedQmlFiles.join(qmlFilesSeparator)) .toUtf8(); } } // namespace QmlProjectManager