aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2022-10-20 13:33:26 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2023-01-23 18:59:47 +0000
commit0182270a9e500346d88c84b6418046bd22f756b3 (patch)
tree5df15f036bb4c24cf1043841d11dbfc3aa3a8a15
parent0efc4ea6bf2d06d3ff402263698dec750c3e341f (diff)
QQmlImport: Handle file selectors in qmldir
With file selectors, a type can exist in the same version under different paths. Detect this case, canonicalize the filename to the selector free version, and rely on the engine to resolve a URL to the correct file. Fixes: QTBUG-107797 Change-Id: I0f74fd37936abfa08547fb439bfa5264e6ca4787 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> (cherry picked from commit 9bfb27be010940ca735ca7dd67a092a03289e27c) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/qml/qml/qqmlimport.cpp62
-rw-r--r--tests/auto/qml/qqmlfileselector/data/qmldirtest/main.qml13
-rw-r--r--tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+linux/MyButton.qml7
-rw-r--r--tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+macos/MyButton.qml7
-rw-r--r--tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/MyButton.qml7
-rw-r--r--tests/auto/qml/qqmlfileselector/data/qmldirtest/qmldir5
-rw-r--r--tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp19
7 files changed, 119 insertions, 1 deletions
diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp
index 75cc0fb8ce..e09b254f46 100644
--- a/src/qml/qml/qqmlimport.cpp
+++ b/src/qml/qml/qqmlimport.cpp
@@ -32,6 +32,8 @@
#include <algorithm>
#include <functional>
+using namespace Qt::Literals::StringLiterals;
+
QT_BEGIN_NAMESPACE
DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
@@ -1034,6 +1036,26 @@ QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlImportDatabase *dat
return stableRelativePath;
}
+/* removes all file selector occurrences in path
+ firstPlus is the position of the initial '+' in the path
+ which we always have as we check for '+' to decide whether
+ we need to do some work at all
+*/
+static QString pathWithoutFileSelectors(QString path, // we want a copy of path
+ qsizetype firstPlus)
+{
+ do {
+ Q_ASSERT(path.at(firstPlus) == u'+');
+ const auto eos = path.size();
+ qsizetype terminatingSlashPos = firstPlus + 1;
+ while (terminatingSlashPos != eos && path.at(terminatingSlashPos) != u'/')
+ ++terminatingSlashPos;
+ path.remove(firstPlus, terminatingSlashPos - firstPlus + 1);
+ firstPlus = path.indexOf(u'+', firstPlus);
+ } while (firstPlus != -1);
+ return path;
+}
+
/*!
\internal
@@ -1080,10 +1102,42 @@ QTypeRevision QQmlImports::matchingQmldirVersion(
typedef QQmlDirComponents::const_iterator ConstIterator;
const QQmlDirComponents &components = qmldir.components();
+ QMultiHash<QString, ConstIterator> baseFileName2ConflictingComponents;
+
ConstIterator cend = components.constEnd();
for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
if (cit2->typeName == cit->typeName && cit2->version == cit->version) {
+ // ugly heuristic to deal with file selectors
+ const auto comp2PotentialFileSelectorPos = cit2->fileName.indexOf(u'+');
+ const bool comp2MightHaveFileSelector = comp2PotentialFileSelectorPos != -1;
+ /* If we detect conflicting paths, we check if they agree when we remove anything looking like a
+ file selector.
+ We need to create copies of the filenames, otherwise QString::replace would modify the
+ existing file-names
+ */
+ QString compFileName1 = cit->fileName;
+ QString compFileName2 = cit2->fileName;
+ if (auto fileSelectorPos1 = compFileName1.indexOf(u'+'); fileSelectorPos1 != -1) {
+ // existing entry was file selector entry, fix it up
+ // it could also be the case that _both_ are using file selectors
+ QString baseName = comp2MightHaveFileSelector ? pathWithoutFileSelectors(compFileName2,
+ comp2PotentialFileSelectorPos)
+ : compFileName2;
+ if (pathWithoutFileSelectors(compFileName1, fileSelectorPos1) == baseName) {
+ baseFileName2ConflictingComponents.insert(baseName, cit);
+ baseFileName2ConflictingComponents.insert(baseName, cit2);
+ continue;
+ }
+ // fall through to error case
+ } else if (comp2MightHaveFileSelector) {
+ // new entry contains file selector (and we now that cit did not)
+ if (pathWithoutFileSelectors(compFileName2, comp2PotentialFileSelectorPos) == compFileName1) {
+ baseFileName2ConflictingComponents.insert(compFileName1, cit2);
+ continue;
+ }
+ // fall through to error case
+ }
// This entry clashes with a predecessor
QQmlError error;
error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
@@ -1097,6 +1151,14 @@ QTypeRevision QQmlImports::matchingQmldirVersion(
addVersion(cit->version);
}
+ // ensure that all components point to the actual base URL, and let the file selectors resolve them correctly during URL resolution
+ for (auto keyIt = baseFileName2ConflictingComponents.keyBegin(); keyIt != baseFileName2ConflictingComponents.keyEnd(); ++keyIt) {
+ const QString& baseFileName = *keyIt;
+ const auto conflictingComponents = baseFileName2ConflictingComponents.values(baseFileName);
+ for (ConstIterator component: conflictingComponents)
+ component->fileName = baseFileName;
+ }
+
typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
const QQmlDirScripts &scripts = qmldir.scripts();
diff --git a/tests/auto/qml/qqmlfileselector/data/qmldirtest/main.qml b/tests/auto/qml/qqmlfileselector/data/qmldirtest/main.qml
new file mode 100644
index 0000000000..d6dd2c9b90
--- /dev/null
+++ b/tests/auto/qml/qqmlfileselector/data/qmldirtest/main.qml
@@ -0,0 +1,13 @@
+import QtQuick
+import qmldirtest
+
+Window {
+ width: 640
+ height: 480
+ visible: true
+ property color color: mybutton.color
+
+ MyButton {
+ id: mybutton
+ }
+}
diff --git a/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+linux/MyButton.qml b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+linux/MyButton.qml
new file mode 100644
index 0000000000..cc6eb967da
--- /dev/null
+++ b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+linux/MyButton.qml
@@ -0,0 +1,7 @@
+import QtQuick
+
+Rectangle {
+ width: 300
+ height: 50
+ color: "blue"
+}
diff --git a/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+macos/MyButton.qml b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+macos/MyButton.qml
new file mode 100644
index 0000000000..5bf632c48d
--- /dev/null
+++ b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/+macos/MyButton.qml
@@ -0,0 +1,7 @@
+import QtQuick
+
+Rectangle {
+ width: 300
+ height: 50
+ color: "yellow"
+}
diff --git a/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/MyButton.qml b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/MyButton.qml
new file mode 100644
index 0000000000..32db428c4f
--- /dev/null
+++ b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qml/MyButton.qml
@@ -0,0 +1,7 @@
+import QtQuick
+
+Rectangle {
+ width: 300
+ height: 50
+ color: "green"
+}
diff --git a/tests/auto/qml/qqmlfileselector/data/qmldirtest/qmldir b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qmldir
new file mode 100644
index 0000000000..ac68d9097d
--- /dev/null
+++ b/tests/auto/qml/qqmlfileselector/data/qmldirtest/qmldir
@@ -0,0 +1,5 @@
+module qmldirtest
+MyButton 1.0 qml/MyButton.qml
+MyButton 1.0 qml/+linux/MyButton.qml
+MyButton 1.0 qml/+macos/MyButton.qml
+
diff --git a/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp b/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp
index 7dd7a68107..46df20378c 100644
--- a/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp
+++ b/tests/auto/qml/qqmlfileselector/tst_qqmlfileselector.cpp
@@ -22,7 +22,7 @@ private slots:
void basicTest();
void basicTestCached();
void applicationEngineTest();
-
+ void qmldirCompatibility();
};
void tst_qqmlfileselector::basicTest()
@@ -70,6 +70,23 @@ void tst_qqmlfileselector::applicationEngineTest()
QCOMPARE(object->property("value").toString(), QString("selected"));
}
+void tst_qqmlfileselector::qmldirCompatibility()
+{
+ QQmlApplicationEngine engine;
+ engine.addImportPath(dataDirectory());
+ engine.load(testFileUrl("qmldirtest/main.qml"));
+ QVERIFY(!engine.rootObjects().isEmpty());
+ QObject *object = engine.rootObjects().at(0);
+ auto color = object->property("color").value<QColor>();
+#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
+ QCOMPARE(color, QColorConstants::Svg::blue);
+#elif defined(Q_OS_DARWIN)
+ QCOMPARE(color, QColorConstants::Svg::yellow);
+#else
+ QCOMPARE(color, QColorConstants::Svg::green);
+#endif
+}
+
QTEST_MAIN(tst_qqmlfileselector)
#include "tst_qqmlfileselector.moc"