diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2022-10-20 13:33:26 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-01-23 18:59:47 +0000 |
commit | 0182270a9e500346d88c84b6418046bd22f756b3 (patch) | |
tree | 5df15f036bb4c24cf1043841d11dbfc3aa3a8a15 | |
parent | 0efc4ea6bf2d06d3ff402263698dec750c3e341f (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>
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" |