From d61172c64ad975291988b73fa709f9e35be11db6 Mon Sep 17 00:00:00 2001 From: EndrII Date: Wed, 26 Oct 2022 10:26:40 +0300 Subject: [PATCH] back port qml scaner from cqtdeployer 1.5 --- src/Deploy/src/extracter.cpp | 12 +- src/Deploy/src/extracter.h | 2 +- src/Deploy/src/iqml.cpp | 13 ++ src/Deploy/src/iqml.h | 40 +++++ src/Deploy/src/{qml.cpp => qmlqt5.cpp} | 47 ++--- src/Deploy/src/{qml.h => qmlqt5.h} | 19 +-- src/Deploy/src/qmlqt6.cpp | 226 +++++++++++++++++++++++++ src/Deploy/src/qmlqt6.h | 45 +++++ tests/units/linux/qmlextracttest.cpp | 4 +- tests/units/linux/qmlscanertest.cpp | 62 +------ 10 files changed, 370 insertions(+), 100 deletions(-) create mode 100644 src/Deploy/src/iqml.cpp create mode 100644 src/Deploy/src/iqml.h rename src/Deploy/src/{qml.cpp => qmlqt5.cpp} (79%) rename src/Deploy/src/{qml.h => qmlqt5.h} (70%) create mode 100644 src/Deploy/src/qmlqt6.cpp create mode 100644 src/Deploy/src/qmlqt6.h diff --git a/src/Deploy/src/extracter.cpp b/src/Deploy/src/extracter.cpp index cffa61f..4ae4206 100644 --- a/src/Deploy/src/extracter.cpp +++ b/src/Deploy/src/extracter.cpp @@ -10,6 +10,7 @@ #include "pluginsparser.h" #include "configparser.h" #include "metafilemanager.h" +#include "qmlqt5.h" #include #include #include @@ -464,13 +465,20 @@ bool Extracter::extractQml() { continue; } - QML ownQmlScaner(cnf->qtDir.getQmls(), cnf->isNeededQt(i.key())); + auto QtVersion = cnf->isNeededQt(i.key()); + QSharedPointer qmlScaner; + if (QtVersion & QtMajorVersion::Qt6) { + qmlScaner = QSharedPointer::create(cnf->qtDir.getQmls()); + } else if (QtVersion & QtMajorVersion::Qt5) { + qmlScaner = QSharedPointer::create(cnf->qtDir.getQmls()); + } - if (!ownQmlScaner.scan(plugins, info.absoluteFilePath())) { + if (qmlScaner && !qmlScaner->scan(plugins, info.absoluteFilePath())) { QuasarAppUtils::Params::log("Failed to run qml scanner", QuasarAppUtils::Error); continue; } + } diff --git a/src/Deploy/src/extracter.h b/src/Deploy/src/extracter.h index 0b893c7..2a07f81 100644 --- a/src/Deploy/src/extracter.h +++ b/src/Deploy/src/extracter.h @@ -14,7 +14,7 @@ #include "dependencymap.h" #include "deploy_global.h" #include "filemanager.h" -#include "qml.h" +#include "qmlqt6.h" class ConfigParser; class MetaFileManager; diff --git a/src/Deploy/src/iqml.cpp b/src/Deploy/src/iqml.cpp new file mode 100644 index 0000000..85d875c --- /dev/null +++ b/src/Deploy/src/iqml.cpp @@ -0,0 +1,13 @@ +//# +//# Copyright (C) 2022-2022 QuasarApp. +//# Distributed under the lgplv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + + +#include "iqml.h" + +iQML::iQML(const QString &qmlRoot) { + _qmlRoot = qmlRoot; +} diff --git a/src/Deploy/src/iqml.h b/src/Deploy/src/iqml.h new file mode 100644 index 0000000..d5b0900 --- /dev/null +++ b/src/Deploy/src/iqml.h @@ -0,0 +1,40 @@ +//# +//# Copyright (C) 2022-2022 QuasarApp. +//# Distributed under the lgplv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#ifndef IQML_H +#define IQML_H + +#include +#include "deploy_global.h" + +/** + * @brief The iQML class Bae interface for scannin qml depends. + */ +class DEPLOYSHARED_EXPORT iQML +{ +public: + + /** + * @brief iQML base constructor + * @param qmlRoot This is qml root dir of Qt destribution. Example: Qt/6.4.0/gcc_64/qml + */ + iQML(const QString& qmlRoot); + virtual ~iQML() = default; + + /** + * @brief scan This method scane qml files in the qmlRoot and return needed to deploy files + * @param res This is result of scan. List of needed to deploy files + * @param _qmlProjectDir This is + * @return true if function finished succesful + */ + virtual bool scan(QStringList &res, const QString &_qmlProjectDir) = 0; + +protected: + QString _qmlRoot; +}; + +#endif // IQML_H diff --git a/src/Deploy/src/qml.cpp b/src/Deploy/src/qmlqt5.cpp similarity index 79% rename from src/Deploy/src/qml.cpp rename to src/Deploy/src/qmlqt5.cpp index f9beb90..de1f04e 100644 --- a/src/Deploy/src/qml.cpp +++ b/src/Deploy/src/qmlqt5.cpp @@ -5,7 +5,7 @@ * of this license document, but changing it is not allowed. */ -#include "qml.h" +#include "qmlqt5.h" #include #include @@ -13,18 +13,18 @@ #include "deploycore.h" #include "qregularexpression.h" -QStringList QML::extractImportLine(const QString& line) const { +QStringList QMLQt5::extractImportLine(const QString& line) const { QStringList result; QStringList list = line.split(" ", splitbehavior); if (list.count() == 3 || (list.count() == 5 && list[3] == "as")) { - if (list[2] == "auto" || (_qtVersion & QtMajorVersion::Qt6)) { + if (list[2] == "auto") { // qt6 result << (list[1].replace(".", "/")); return result; } // qt5 - result << (list[2][0] + QString("#") + list[1].replace(".", "/")); + result << (QString(list[2][0]) + "#" + list[1].replace(".", "/")); } else if (list.count() == 2 || (list.count() == 4 && list[2] == "as")) { // qt6 result << (list[1].replace(".", "/")); @@ -33,7 +33,7 @@ QStringList QML::extractImportLine(const QString& line) const { return result; } -QStringList QML::extractImportsFromFile(const QString &filepath) const { +QStringList QMLQt5::extractImportsFromFile(const QString &filepath) const { QStringList imports; QFile F(filepath); if (!F.open(QIODevice::ReadOnly)) return QStringList(); @@ -65,7 +65,7 @@ QStringList QML::extractImportsFromFile(const QString &filepath) const { return imports; } -bool QML::extractImportsFromDir(const QString &path, bool recursive) { +bool QMLQt5::extractImportsFromDir(const QString &path, bool recursive) { QDir dir(path); if (!dir.isReadable()) { @@ -82,33 +82,22 @@ bool QML::extractImportsFromDir(const QString &path, bool recursive) { for (const auto &import : qAsConst(imports)) { if (!_imports.contains(import)) { _imports.insert(import); - extractImportsFromDir(getPathFromImport(import), false); + extractImportsFromDir(getPathFromImport(import), recursive); } } } for (const auto& module: qAsConst(qmlmodule)) { QStringList imports = extractImportsFromQmlModule(module.absoluteFilePath()); - imports += extractImportsFromFile(module.absoluteFilePath()); for (const auto &import : qAsConst(imports)) { if (!_imports.contains(import)) { _imports.insert(import); - extractImportsFromDir(getPathFromImport(import), false); + extractImportsFromDir(getPathFromImport(import), recursive); } } } - // task https://github.com/QuasarApp/CQtDeployer/issues/600 - // There are no import lines for the qt models module in Qt 6.1, but this module is required for all qml applications. - if (_qtVersion & QtMajorVersion::Qt6) { - auto importQtQml = "QtQml"; - if (!_imports.contains(importQtQml)) { - _imports.insert(importQtQml); - extractImportsFromDir(getPathFromImport(importQtQml), false); - } - } - if (recursive) { for (const auto &info: dirs) { extractImportsFromDir(info.absoluteFilePath(), recursive); @@ -118,7 +107,7 @@ bool QML::extractImportsFromDir(const QString &path, bool recursive) { return true; } -QString QML::getPathFromImport(const QString &import, bool checkVersions) { +QString QMLQt5::getPathFromImport(const QString &import, bool checkVersions) { if (!import.contains("#")) { // qt 6 auto info = QFileInfo(_qmlRoot + "/" + import); @@ -136,6 +125,7 @@ QString QML::getPathFromImport(const QString &import, bool checkVersions) { } else { return ""; } + QRegularExpression matcher; matcher.setPattern("[/\\\\]"); auto words = importData.value(index).split(matcher); @@ -161,7 +151,7 @@ QString QML::getPathFromImport(const QString &import, bool checkVersions) { return info.absoluteFilePath(); } -bool QML::deployPath(const QString &path, QStringList &res) { +bool QMLQt5::deployPath(const QString &path, QStringList &res) { QDir dir(path); auto infoList = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); @@ -178,7 +168,7 @@ bool QML::deployPath(const QString &path, QStringList &res) { return true; } -bool QML::scanQmlTree(const QString &qmlTree) { +bool QMLQt5::scanQmlTree(const QString &qmlTree) { QDir dir(qmlTree); if (!dir.isReadable()) { @@ -198,7 +188,7 @@ bool QML::scanQmlTree(const QString &qmlTree) { return true; } -QStringList QML::extractImportsFromQmlModule(const QString &module) const { +QStringList QMLQt5::extractImportsFromQmlModule(const QString &module) const { QStringList imports; QFile F(module); if (!F.open(QIODevice::ReadOnly)) return QStringList(); @@ -217,17 +207,12 @@ QStringList QML::extractImportsFromQmlModule(const QString &module) const { return imports; } -void QML::setQtVersion(const QtMajorVersion &qtVersion) { - _qtVersion = qtVersion; -} -QML::QML(const QString &qmlRoot, QtMajorVersion qtVersion) { - _qmlRoot = qmlRoot; - setQtVersion(qtVersion); +QMLQt5::QMLQt5(const QString &qmlRoot): iQML(qmlRoot) {} -} +QMLQt5::~QMLQt5() {} -bool QML::scan(QStringList &res, const QString& _qmlProjectDir) { +bool QMLQt5::scan(QStringList &res, const QString& _qmlProjectDir) { if (!scanQmlTree(_qmlRoot)) { return false; diff --git a/src/Deploy/src/qml.h b/src/Deploy/src/qmlqt5.h similarity index 70% rename from src/Deploy/src/qml.h rename to src/Deploy/src/qmlqt5.h index eac9dba..b062c1b 100644 --- a/src/Deploy/src/qml.h +++ b/src/Deploy/src/qmlqt5.h @@ -5,15 +5,14 @@ //# of this license document, but changing it is not allowed. //# -#ifndef QML_DEPLY_H -#define QML_DEPLY_H +#ifndef QML_QT5_DEPLY_H +#define QML_QT5_DEPLY_H #include #include -#include "deploy_global.h" -#include "deploycore.h" +#include "iqml.h" -class DEPLOYSHARED_EXPORT QML { +class DEPLOYSHARED_EXPORT QMLQt5: public iQML { private: QStringList extractImportsFromFile(const QString &filepath) const; @@ -25,22 +24,20 @@ private: bool deployPath( const QString& path, QStringList& res); bool scanQmlTree(const QString& qmlTree); QStringList extractImportsFromQmlModule(const QString& module) const; - QString _qmlRoot = ""; QSet _imports; QSet secondVersions; - QtMajorVersion _qtVersion = QtMajorVersion::Qt5; QStringList extractImportLine(const QString &line) const; public: - QML(const QString& qmlRoot, QtMajorVersion isQt6); + QMLQt5(const QString& qmlRoot); + ~QMLQt5() override; - bool scan(QStringList &res, const QString &_qmlProjectDir); - void setQtVersion(const QtMajorVersion &qtVersion); + bool scan(QStringList &res, const QString &_qmlProjectDir) override; friend class deploytest; friend class QmlScanerTest; friend class QMLExtractTest; }; -#endif // QML_DEPLY_H +#endif // QML_QT5_DEPLY_H diff --git a/src/Deploy/src/qmlqt6.cpp b/src/Deploy/src/qmlqt6.cpp new file mode 100644 index 0000000..9784e55 --- /dev/null +++ b/src/Deploy/src/qmlqt6.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2018-2022 QuasarApp. + * Distributed under the lgplv3 software license, see the accompanying + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +#include "qmlqt6.h" + +#include +#include +#include "defines.h" +#include "deploycore.h" +#include "qregularexpression.h" + +QStringList QMLQt6::extractImportLine(const QString& line) const { + QStringList result; + QStringList list = line.split(" ", splitbehavior); + + result << (list[1].replace(".", "/")); + return result; + + return result; +} + +QStringList QMLQt6::extractImportsFromFile(const QString &filepath) const { + QStringList imports; + QFile F(filepath); + if (!F.open(QIODevice::ReadOnly)) return QStringList(); + + QString content = F.readAll(); + QRegularExpression matcher; + + matcher.setPattern("\\{(.*)\\}"); + content.remove(matcher); + + matcher.setPattern("/\\*(.*)\\*/"); + content.remove(matcher); + + // Replace optional imports to restrict option, because before Qt 6.3 The Qml app needee of optional qml packas. + content.replace("optional import", "import"); + + + const auto list = content.split("\n"); + for (const QString &line : list) + for (QString &word : line.split(";", splitbehavior)) + { + word = word.simplified(); + if (word.startsWith("//")) continue; + if (!word.startsWith("import")) continue; + + imports += extractImportLine(word); + } + + return imports; +} + +bool QMLQt6::extractImportsFromDir(const QString &path, bool recursive) { + QDir dir(path); + + if (!dir.isReadable()) { + return false; + } + + auto files = dir.entryInfoList(QStringList() << "*.qml" << "*.QML", QDir::Files); + auto qmlmodule = dir.entryInfoList(QStringList() << "qmldir", QDir::Files); + + auto dirs = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs); + + for (const auto &info: files) { + auto imports = extractImportsFromFile(info.absoluteFilePath()); + for (const auto &import : qAsConst(imports)) { + if (!_imports.contains(import)) { + _imports.insert(import); + extractImportsFromDir(getPathFromImport(import), false); + } + } + } + + for (const auto& module: qAsConst(qmlmodule)) { + QStringList imports = extractImportsFromQmlModule(module.absoluteFilePath()); + imports += extractImportsFromFile(module.absoluteFilePath()); + + for (const auto &import : qAsConst(imports)) { + if (!_imports.contains(import)) { + _imports.insert(import); + extractImportsFromDir(getPathFromImport(import), false); + } + } + } + + // task https://github.com/QuasarApp/CQtDeployer/issues/600 + // There are no import lines for the qt models module in Qt 6.1, but this module is required for all qml applications. + auto importQtQml = "QtQml"; + if (!_imports.contains(importQtQml)) { + _imports.insert(importQtQml); + extractImportsFromDir(getPathFromImport(importQtQml), false); + } + + if (recursive) { + for (const auto &info: dirs) { + extractImportsFromDir(info.absoluteFilePath(), recursive); + } + } + + return true; +} + +QString QMLQt6::getPathFromImport(const QString &import, bool checkVersions) { + if (!import.contains("#")) { + // qt 6 + auto info = QFileInfo(_qmlRoot + "/" + import); + return info.absoluteFilePath(); + } + + auto importData = import.split("#"); + + int index; + + if (importData.size() == 2) + index = 1; + else if (!importData.isEmpty()) { + index = 0; + } else { + return ""; + } + QRegularExpression matcher; + matcher.setPattern("[/\\\\]"); + auto words = importData.value(index).split(matcher); + const bool isSecond = importData.first() == "2" && checkVersions; + bool secondVersion = isSecond; + + QString path; + for (auto i = words.rbegin(); i != words.rend(); ++i) { + QString word = *i; + if (secondVersion && secondVersions.contains(word)) { + secondVersion = false; + word.push_back(".2"); + } + + path.push_front(word + "/"); + } + auto info = QFileInfo(_qmlRoot + "/" + path); + + if (isSecond && !info.exists()) { + return getPathFromImport(import, false); + } + + return info.absoluteFilePath(); +} + +bool QMLQt6::deployPath(const QString &path, QStringList &res) { + QDir dir(path); + auto infoList = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + + for (const auto &info : qAsConst(infoList)) { + if (DeployCore::isDebugFile(info.fileName())) { + QuasarAppUtils::Params::log("Skip debug library " + + info.absoluteFilePath()); + continue; + } + + res.push_back(info.absoluteFilePath()); + } + + return true; +} + +bool QMLQt6::scanQmlTree(const QString &qmlTree) { + QDir dir(qmlTree); + + if (!dir.isReadable()) { + return false; + } + + auto list = dir.entryInfoList( QDir::Dirs | QDir::NoDotAndDotDot); + + for (const auto &info : list) { + if (info.fileName().contains(".2")) { + secondVersions.insert(info.fileName().left(info.fileName().size() - 2)); + } + scanQmlTree(info.absoluteFilePath()); + + } + + return true; +} + +QStringList QMLQt6::extractImportsFromQmlModule(const QString &module) const { + QStringList imports; + QFile F(module); + if (!F.open(QIODevice::ReadOnly)) return QStringList(); + + QString content = F.readAll(); + const auto list = content.split("\n"); + for (QString line : list) { + line = line.simplified(); + if (line.startsWith("//") || line.startsWith("#")) continue; + if (!line.startsWith("depends")) continue; + + imports += extractImportLine(line); + } + + return imports; +} + +QMLQt6::QMLQt6(const QString &qmlRoot): iQML(qmlRoot) {} + +QMLQt6::~QMLQt6(){} + +bool QMLQt6::scan(QStringList &res, const QString& _qmlProjectDir) { + + if (!scanQmlTree(_qmlRoot)) { + return false; + } + + if (!extractImportsFromDir(_qmlProjectDir, true)) { + return false; + } + + for (const auto &import : qAsConst(_imports)) { + res.push_back(getPathFromImport(import)); + } + + return true; +} diff --git a/src/Deploy/src/qmlqt6.h b/src/Deploy/src/qmlqt6.h new file mode 100644 index 0000000..c4c6c46 --- /dev/null +++ b/src/Deploy/src/qmlqt6.h @@ -0,0 +1,45 @@ +//# +//# Copyright (C) 2018-2022 QuasarApp. +//# Distributed under the lgplv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#ifndef QML_QT6_DEPLY_H +#define QML_QT6_DEPLY_H + +#include +#include +#include "iqml.h" + +class DEPLOYSHARED_EXPORT QMLQt6: public iQML { + +public: + QMLQt6(const QString& qmlRoot); + ~QMLQt6() override; + + bool scan(QStringList &res, const QString &_qmlProjectDir) override; + + +private: + + QStringList extractImportsFromFile(const QString &filepath) const; + + bool extractImportsFromDir(const QString &path, bool recursive = false); + + QString getPathFromImport(const QString& import, bool checkVersions = true); + + bool deployPath( const QString& path, QStringList& res); + bool scanQmlTree(const QString& qmlTree); + QStringList extractImportsFromQmlModule(const QString& module) const; + QStringList extractImportLine(const QString &line) const; + + QSet _imports; + QSet secondVersions; + + friend class deploytest; + friend class QmlScanerTest; + friend class QMLExtractTest; +}; + +#endif // QML_QT6_DEPLY_H diff --git a/tests/units/linux/qmlextracttest.cpp b/tests/units/linux/qmlextracttest.cpp index 3023f5f..b20f9d1 100644 --- a/tests/units/linux/qmlextracttest.cpp +++ b/tests/units/linux/qmlextracttest.cpp @@ -7,7 +7,7 @@ #include "qmlextracttest.h" -#include "qml.h" +#include "qmlqt5.h" #include "qmlcreator.h" #include #include @@ -23,7 +23,7 @@ void QMLExtractTest::test() { auto qmlFiles = creator.getCopyedQml(); - QML scaner("./", QtMajorVersion::Qt5); + QMLQt5 scaner("./"); for (const auto &file : qAsConst(qmlFiles)) { diff --git a/tests/units/linux/qmlscanertest.cpp b/tests/units/linux/qmlscanertest.cpp index 80a4ed4..780a576 100644 --- a/tests/units/linux/qmlscanertest.cpp +++ b/tests/units/linux/qmlscanertest.cpp @@ -7,7 +7,7 @@ #include "qmlscanertest.h" -#include "qml.h" +#include "qmlqt6.h" #include #include #include @@ -18,80 +18,36 @@ void QmlScanerTest::test() { // qt5 auto qmlRoot = QFileInfo(TestQtDir + "/qml").absoluteFilePath(); - QML *scaner = new QML(qmlRoot, QtMajorVersion::Qt5); - auto imports = scaner->extractImportsFromFile(":/qmlFile.qml"); - scaner->scanQmlTree(qmlRoot); - - QSet results = { - {qmlRoot + "/QtQuick.2/"}, - {qmlRoot + "/QtQuick/Controls.2/"}, - {qmlRoot + "/QtQuick/Controls.2/Material/"}, - {qmlRoot + "/QtQuick/Layouts/"}, - }; - - QVERIFY(results.size() == imports.size()); - -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - - for (const auto &import: qAsConst(imports)) { - auto path = scaner->getPathFromImport(import); - QVERIFY(results.contains(path)); - } -#endif + QMLQt6 scaner6(qmlRoot); // qt6 - scaner->setQtVersion(QtMajorVersion::Qt6); - results = { + QSet results = { {qmlRoot + "/QtQuick"}, {qmlRoot + "/QtQuick/Controls"}, {qmlRoot + "/QtQuick/Controls/Material"}, {qmlRoot + "/QtQuick/Layouts"}, }; - imports = scaner->extractImportsFromFile(":/qmlFileQt6.qml"); + auto imports = scaner6.extractImportsFromFile(":/qmlFileQt6.qml"); QVERIFY(results.size() == imports.size()); for (const auto &import: qAsConst(imports)) { - auto path = scaner->getPathFromImport(import); + auto path = scaner6.getPathFromImport(import); QVERIFY(results.contains(path)); } - imports = scaner->extractImportsFromFile(":/qmlFile.qml"); + imports = scaner6.extractImportsFromFile(":/qmlFile.qml"); QVERIFY(results.size() == imports.size()); for (const auto & import: qAsConst(imports)) { - auto path = scaner->getPathFromImport(import); + auto path = scaner6.getPathFromImport(import); QVERIFY(results.contains(path)); } -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - - // qt5 - scaner->setQtVersion(QtMajorVersion::Qt5); - - results = { - {qmlRoot + "/QtQuick.2/"}, - {qmlRoot + "/QtQuick/Window.2/"}, - {qmlRoot + "/QtQuick/Layouts/"}, - {qmlRoot + "/Qt/labs/folderlistmodel/"}, - {qmlRoot + "/QtQuick/VirtualKeyboard/Settings/"}, - {qmlRoot + "/QtQuick/VirtualKeyboard/Styles/"}, - }; - - imports = scaner->extractImportsFromQmlModule(":/qmlDir"); - - QVERIFY(results.size() == imports.size()); - - for (const auto &import: qAsConst(imports)) { - auto path = scaner->getPathFromImport(import); - QVERIFY(results.contains(path)); - } -#endif // qt6 - scaner->setQtVersion(QtMajorVersion::Qt6); results = { {qmlRoot + "/QtQuick"}, @@ -102,12 +58,12 @@ void QmlScanerTest::test() { {qmlRoot + "/QtQuick/VirtualKeyboard/Styles"}, }; - imports = scaner->extractImportsFromQmlModule(":/qmlDir"); + imports = scaner6.extractImportsFromQmlModule(":/qmlDir"); QVERIFY(results.size() == imports.size()); for (const auto &import: qAsConst(imports)) { - auto path = scaner->getPathFromImport(import); + auto path = scaner6.getPathFromImport(import); QVERIFY(results.contains(path)); }