QML: Warn about relative QRC path

A runtime warning will now be issued if a relative QRC path is passed:
a relative QRC path doesn't make a lot of sense, but it works
to some extent (therefore it's only a warning).

Task-number: QTCREATORBUG-32198
Change-Id: Ifd55013d01589149128ce5d3b31fd6748186fd05
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Andrii Semkiv 2025-01-20 16:47:41 +01:00
parent c27daa01f5
commit bfdadbac48
6 changed files with 83 additions and 12 deletions

View File

@ -11,6 +11,8 @@
#include <QtQml/private/qqmldirdata_p.h>
#include <QtQml/private/qqmlfileselector_p.h>
using namespace Qt::Literals::StringLiterals;
QT_BEGIN_NAMESPACE
QQmlApplicationEnginePrivate::QQmlApplicationEnginePrivate(QQmlEngine *e)
@ -85,18 +87,19 @@ void QQmlApplicationEnginePrivate::_q_loadTranslations()
#endif
}
static QString translationsDirectoryFromLocalUrl(const QUrl &url)
{
QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
return fi.path() + "/i18n"_L1;
}
void QQmlApplicationEnginePrivate::startLoad(const QUrl &url, const QByteArray &data, bool dataFlag)
{
Q_Q(QQmlApplicationEngine);
ensureInitialized();
if (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc")) {
QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
translationsDirectory = fi.path() + QLatin1String("/i18n");
} else {
translationsDirectory.clear();
}
updateTranslationDirectory(url);
_q_loadTranslations(); //Translations must be loaded before the QML file is
QQmlComponent *c = new QQmlComponent(q, q);
@ -125,12 +128,9 @@ void QQmlApplicationEnginePrivate::startLoad(QAnyStringView uri, QAnyStringView
if (type.sourceUrl().isValid()) {
const auto qmlDirData = typeLoader.getQmldir(type.sourceUrl());
const QUrl url = qmlDirData->finalUrl();
if (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc")) {
QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
translationsDirectory = fi.path() + QLatin1String("/i18n");
} else {
translationsDirectory.clear();
}
// A QRC URL coming from a qmldir cannot contain a relative path
Q_ASSERT(url.scheme() != "qrc"_L1 || url.path().startsWith('/'_L1));
updateTranslationDirectory(url);
}
/* Translations must be loaded before the QML file. They require translationDirectory to
@ -188,6 +188,18 @@ void QQmlApplicationEnginePrivate::ensureLoadingFinishes(QQmlComponent *c)
QObject::connect(c, &QQmlComponent::statusChanged, q, [this, c] { this->finishLoad(c); });
}
void QQmlApplicationEnginePrivate::updateTranslationDirectory(const QUrl &url)
{
const QString scheme = url.scheme();
if (scheme == "file"_L1) {
translationsDirectory = translationsDirectoryFromLocalUrl(url);
} else if (scheme == "qrc"_L1) {
translationsDirectory = translationsDirectoryFromLocalUrl(url);
} else {
translationsDirectory.clear();
}
}
/*!
\class QQmlApplicationEngine
\since 5.1

View File

@ -39,6 +39,8 @@ public:
void _q_loadTranslations();
void finishLoad(QQmlComponent *component);
void ensureLoadingFinishes(QQmlComponent *component);
void updateTranslationDirectory(const QUrl &url);
QList<QObject *> objects;
QVariantMap initialProperties;
QStringList extraFileSelectors;

View File

@ -797,6 +797,12 @@ void QQmlComponentPrivate::loadUrl(const QUrl &newUrl, QQmlComponent::Compilatio
m_url = newUrl;
}
if (m_url.scheme() == "qrc"_L1 && !m_url.path().startsWith("/"_L1)) {
qWarning().nospace().noquote()
<< "QQmlComponent: attempted to load via a relative URL '" << m_url.toString()
<< "' in resource file system. This is not fully supported and may not work";
}
if (newUrl.isEmpty()) {
QQmlError error;
error.setDescription(QQmlComponent::tr("Invalid empty URL"));

View File

@ -11,6 +11,8 @@
#include <QDebug>
#include <QtQuickTestUtils/private/qmlutils_p.h>
using namespace Qt::Literals::StringLiterals;
class tst_qqmlapplicationengine : public QQmlDataTest
{
Q_OBJECT
@ -33,6 +35,7 @@ private slots:
void translationChange();
void setInitialProperties();
void failureToLoadTriggersWarningSignal();
void relativeQrcPathTriggersWarningSignal();
void errorWhileCreating();
void createWithQrcPath();
@ -370,6 +373,24 @@ void tst_qqmlapplicationengine::failureToLoadTriggersWarningSignal()
QTRY_COMPARE(warningObserver.size(), 1);
}
void tst_qqmlapplicationengine::relativeQrcPathTriggersWarningSignal()
{
const auto url = QUrl("qrc:main.qml");
qRegisterMetaType<QList<QQmlError>>();
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlApplicationEngine failed to load component");
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
"QQmlComponent: attempted to load via a relative URL '%1' in resource file system. "
"This is not fully supported and may not work"_L1.arg(url.toString())
.toLocal8Bit()
.data());
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "qrc:main.qml: No such file or directory");
QQmlApplicationEngine test;
const QSignalSpy warningObserver(&test, &QQmlApplicationEngine::warnings);
test.load(url);
QTRY_COMPARE(warningObserver.size(), 1);
}
void tst_qqmlapplicationengine::errorWhileCreating()
{
auto url = testFileUrl("requiredViolation.qml");

View File

@ -151,6 +151,7 @@ private slots:
void loadFromModuleRequired();
void loadUrlRequired();
void loadFromQrc();
void loadFromRelativeQrcPath();
void removeBinding();
void complexObjectArgument();
void bindingEvaluationOrder();
@ -1636,6 +1637,20 @@ void tst_qqmlcomponent::loadFromQrc()
QVERIFY(p->compilationUnit()->baseCompilationUnit()->aotCompiledFunctions);
}
void tst_qqmlcomponent::loadFromRelativeQrcPath()
{
QQmlEngine e;
QQmlComponent c(&e);
const QUrl url = QUrl("qrc:main.qml");
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
"QQmlComponent: attempted to load via a relative URL '%1' in resource file system. "
"This is not fully supported and may not work"_L1.arg(url.toString())
.toLocal8Bit()
.data());
c.loadUrl(url);
}
void tst_qqmlcomponent::removeBinding()
{
QQmlEngine e;

View File

@ -33,6 +33,7 @@ private slots:
void fromModuleCtor();
void loadFromModule_data();
void loadFromModule();
void setSourceRelativeQrcPath();
void overlay();
};
@ -343,6 +344,20 @@ void tst_QQuickView::loadFromModule()
QCOMPARE(view.source(), url);
}
void tst_QQuickView::setSourceRelativeQrcPath()
{
QQuickView view;
const auto url = QUrl("qrc:main.qml");
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
"QQmlComponent: attempted to load via a relative URL '%1' in resource file system. "
"This is not fully supported and may not work"_L1.arg(url.toString())
.toLocal8Bit()
.data());
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "qrc:main.qml: No such file or directory");
view.setSource(url);
}
void tst_QQuickView::overlay()
{
QTest::ignoreMessage(QtWarningMsg,