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. Pick-to: 6.5 6.4 6.2 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>
This commit is contained in:
parent
444d4f1f3f
commit
9bfb27be01
|
@ -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();
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import QtQuick
|
||||
import qmldirtest
|
||||
|
||||
Window {
|
||||
width: 640
|
||||
height: 480
|
||||
visible: true
|
||||
property color color: mybutton.color
|
||||
|
||||
MyButton {
|
||||
id: mybutton
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
width: 300
|
||||
height: 50
|
||||
color: "blue"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
width: 300
|
||||
height: 50
|
||||
color: "yellow"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
width: 300
|
||||
height: 50
|
||||
color: "green"
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue