aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-03-01 10:11:18 +0100
committerUlf Hermann <ulf.hermann@qt.io>2021-03-04 23:35:39 +0100
commitce52c245aaccf3183ef4759882e25b51b539195a (patch)
tree5229da9fac5299d95671e551bd2e513942b71d02
parent3881f0df3d115ba8e59a5cedea970f3b085aa84a (diff)
Add a "prefer" directive to qmldir
The argument is the path from which further files from the module should be loaded. This allows us to load potentially pre-compiled files from a resource contained in a plugin. The qmldir file has to be stored outside the plugin in order for the QML engine to find it, but the QML files can now be stored inside the plugin. [ChangeLog][QtQml] You can now specify that QML files in a QML module should be loaded from a different place than the directory where the qmldir file is found. To do this, add a "prefer" directive to the qmldir file. The most useful application of this is adding the QML files as resources to a plugin, and using qmlcachegen to pre-compile them. You can then add their path inside the resource file system as "prefer" path in order to make the engine load the pre-compiled files. The plain files should still be installed next to the qmldir file so that they remain visible to tooling. Change-Id: Ib281b39230621b3762432095a47fb499412cbaa6 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc16
-rw-r--r--src/qml/qml/qqmlimport.cpp14
-rw-r--r--src/qml/qml/qqmltypeloaderqmldircontent.cpp5
-rw-r--r--src/qml/qml/qqmltypeloaderqmldircontent_p.h1
-rw-r--r--src/qml/qmldirparser/qqmldirparser.cpp27
-rw-r--r--src/qml/qmldirparser/qqmldirparser_p.h2
-rw-r--r--tests/auto/qml/qqmldirparser/data/prefer-no-slash/qmldir5
-rw-r--r--tests/auto/qml/qqmldirparser/data/prefer/qmldir5
-rw-r--r--tests/auto/qml/qqmldirparser/data/two-prefer/qmldir6
-rw-r--r--tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp57
-rw-r--r--tests/auto/qml/qqmlimport/CMakeLists.txt7
-rw-r--r--tests/auto/qml/qqmlimport/Preferred.qml5
-rw-r--r--tests/auto/qml/qqmlimport/data/ModuleWithPrefer/Preferred.qml5
-rw-r--r--tests/auto/qml/qqmlimport/data/ModuleWithPrefer/qmldir3
-rw-r--r--tests/auto/qml/qqmlimport/data/prefer.qml3
-rw-r--r--tests/auto/qml/qqmlimport/tst_qqmlimport.cpp12
16 files changed, 171 insertions, 2 deletions
diff --git a/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc b/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
index 8f6aa0ae7c..95e8a179de 100644
--- a/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
+++ b/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
@@ -296,6 +296,22 @@ designersupported
The items of an unsupported plugin are not painted in the Qt Quick Designer,
but they are still available as empty boxes and the properties can be edited.
+ \row
+ \li
+ \code
+prefer <Path>
+ \endcode
+
+ \li This property directs the QML engine to load any further files for this
+ module from <path>, rather than the current directory. This can be used
+ to load files compiled with qmlcachegen.
+
+ For example, you can add a module's QML files as resources to a resource
+ path \c{:/my/path/MyModule/}. Then, add \c{prefer :/my/path/MyModule} to
+ the qmldir file in order to use the files in the resource system, rather
+ than the ones in the file system. If you then use qmlcachegen for those,
+ the pre-compiled files will be available to any clients of the module.
+
\endtable
Each command in a \c qmldir file must be on a separate line.
diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp
index 535b6713ba..0016d4bcc4 100644
--- a/src/qml/qml/qqmlimport.cpp
+++ b/src/qml/qml/qqmlimport.cpp
@@ -654,8 +654,18 @@ bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl,
const QQmlTypeLoaderQmldirContent &qmldir,
QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
{
- Q_ASSERT(resolvedUrl.endsWith(Slash));
- url = resolvedUrl;
+
+ const QString preferredPath = qmldir.preferredPath();
+ if (preferredPath.isEmpty()) {
+ Q_ASSERT(resolvedUrl.endsWith(Slash));
+ url = resolvedUrl;
+ } else {
+ Q_ASSERT(preferredPath.endsWith(Slash));
+ if (preferredPath.startsWith(u':'))
+ url = QStringLiteral("qrc") + preferredPath;
+ else
+ url = QUrl::fromLocalFile(preferredPath).toString();
+ }
qmlDirComponents = qmldir.components();
diff --git a/src/qml/qml/qqmltypeloaderqmldircontent.cpp b/src/qml/qml/qqmltypeloaderqmldircontent.cpp
index 0eddf796d7..ca9f5fc80d 100644
--- a/src/qml/qml/qqmltypeloaderqmldircontent.cpp
+++ b/src/qml/qml/qqmltypeloaderqmldircontent.cpp
@@ -115,6 +115,11 @@ QString QQmlTypeLoaderQmldirContent::qmldirLocation() const
return m_location;
}
+QString QQmlTypeLoaderQmldirContent::preferredPath() const
+{
+ return m_parser.preferredPath();
+}
+
bool QQmlTypeLoaderQmldirContent::designerSupported() const
{
return m_parser.designerSupported();
diff --git a/src/qml/qml/qqmltypeloaderqmldircontent_p.h b/src/qml/qml/qqmltypeloaderqmldircontent_p.h
index f9b68f3410..11f98b5c63 100644
--- a/src/qml/qml/qqmltypeloaderqmldircontent_p.h
+++ b/src/qml/qml/qqmltypeloaderqmldircontent_p.h
@@ -81,6 +81,7 @@ public:
QQmlDirImports imports() const;
QString qmldirLocation() const;
+ QString preferredPath() const;
bool designerSupported() const;
diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp
index 403c04b1e1..1dbf35f1d3 100644
--- a/src/qml/qmldirparser/qqmldirparser.cpp
+++ b/src/qml/qmldirparser/qqmldirparser.cpp
@@ -299,6 +299,28 @@ bool QQmlDirParser::parse(const QString &source)
|| sections[0] == QLatin1String("depends")) {
if (!readImport(sections, sectionCount, Import::Default))
continue;
+ } else if (sections[0] == QLatin1String("prefer")) {
+ if (sectionCount < 2) {
+ reportError(lineNumber, 0,
+ QStringLiteral("prefer directive requires one argument, "
+ "but %1 were provided").arg(sectionCount - 1));
+ continue;
+ }
+
+ if (!_preferredPath.isEmpty()) {
+ reportError(lineNumber, 0, QStringLiteral(
+ "only one prefer directive may be defined in a qmldir file"));
+ continue;
+ }
+
+ if (!sections[1].endsWith(u'/')) {
+ // Yes. People should realize it's a directory.
+ reportError(lineNumber, 0, QStringLiteral(
+ "the preferred directory has to end with a '/'"));
+ continue;
+ }
+
+ _preferredPath = sections[1];
} else if (sectionCount == 2) {
// No version specified (should only be used for relative qmldir files)
const Component entry(sections[0], sections[1], QTypeRevision());
@@ -416,6 +438,11 @@ QStringList QQmlDirParser::classNames() const
return _classNames;
}
+QString QQmlDirParser::preferredPath() const
+{
+ return _preferredPath;
+}
+
QDebug &operator<< (QDebug &debug, const QQmlDirParser::Component &component)
{
const QString output = QStringLiteral("{%1 %2.%3}")
diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h
index d71dbfe9dc..eabaceae91 100644
--- a/src/qml/qmldirparser/qqmldirparser_p.h
+++ b/src/qml/qmldirparser/qqmldirparser_p.h
@@ -160,6 +160,7 @@ public:
QStringList typeInfos() const;
QStringList classNames() const;
+ QString preferredPath() const;
private:
bool maybeAddComponent(const QString &typeName, const QString &fileName, const QString &version, QHash<QString,Component> &hash, int lineNumber = -1, bool multi = true);
@@ -168,6 +169,7 @@ private:
private:
QList<QQmlJS::DiagnosticMessage> _errors;
QString _typeNamespace;
+ QString _preferredPath;
QMultiHash<QString,Component> _components;
QList<Import> _dependencies;
QList<Import> _imports;
diff --git a/tests/auto/qml/qqmldirparser/data/prefer-no-slash/qmldir b/tests/auto/qml/qqmldirparser/data/prefer-no-slash/qmldir
new file mode 100644
index 0000000000..3ad24eed20
--- /dev/null
+++ b/tests/auto/qml/qqmldirparser/data/prefer-no-slash/qmldir
@@ -0,0 +1,5 @@
+module FooBar
+classname FooBarInstance
+prefer :/foo/bar
+plugin doesnotexist
+Foo 1.0 Foo.qml
diff --git a/tests/auto/qml/qqmldirparser/data/prefer/qmldir b/tests/auto/qml/qqmldirparser/data/prefer/qmldir
new file mode 100644
index 0000000000..06cb227c43
--- /dev/null
+++ b/tests/auto/qml/qqmldirparser/data/prefer/qmldir
@@ -0,0 +1,5 @@
+module FooBar
+classname FooBarInstance
+prefer :/foo/bar/
+plugin doesnotexist
+Foo 1.0 Foo.qml
diff --git a/tests/auto/qml/qqmldirparser/data/two-prefer/qmldir b/tests/auto/qml/qqmldirparser/data/two-prefer/qmldir
new file mode 100644
index 0000000000..08a5ca6834
--- /dev/null
+++ b/tests/auto/qml/qqmldirparser/data/two-prefer/qmldir
@@ -0,0 +1,6 @@
+module FooBar
+classname FooBarInstance
+prefer :/foo/bar/
+prefer :/wrong/wrong/
+plugin doesnotexist
+Foo 1.0 Foo.qml
diff --git a/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp b/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp
index d53de4fd52..8f0c58653b 100644
--- a/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp
+++ b/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp
@@ -150,6 +150,7 @@ namespace {
void tst_qqmldirparser::parse_data()
{
QTest::addColumn<QString>("file");
+ QTest::addColumn<QString>("preferredPath");
QTest::addColumn<QStringList>("errors");
QTest::addColumn<QStringList>("plugins");
QTest::addColumn<QStringList>("classnames");
@@ -160,6 +161,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("empty")
<< "empty/qmldir"
+ << QString()
<< QStringList()
<< QStringList()
<< QStringList()
@@ -170,6 +172,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("no-content")
<< "no-content/qmldir"
+ << QString()
<< QStringList()
<< QStringList()
<< QStringList()
@@ -180,6 +183,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("one-section")
<< "one-section/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: a component declaration requires two or three arguments, but 1 were provided")
<< QStringList()
<< QStringList()
@@ -190,6 +194,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("four-sections")
<< "four-sections/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: a component declaration requires two or three arguments, but 4 were provided")
<< QStringList()
<< QStringList()
@@ -200,6 +205,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("incomplete-module")
<< "incomplete-module/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: module identifier directive requires one argument, but 0 were provided")
<< QStringList()
<< QStringList()
@@ -210,6 +216,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("excessive-module")
<< "excessive-module/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: module identifier directive requires one argument, but 2 were provided")
<< QStringList()
<< QStringList()
@@ -220,6 +227,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("repeated-module")
<< "repeated-module/qmldir"
+ << QString()
<< (QStringList() << "qmldir:2: only one module identifier directive may be defined in a qmldir file")
<< QStringList()
<< QStringList()
@@ -230,6 +238,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("non-first-module")
<< "non-first-module/qmldir"
+ << QString()
<< (QStringList() << "qmldir:2: module identifier directive must be the first directive in a qmldir file")
<< (QStringList() << "foo|")
<< QStringList()
@@ -240,6 +249,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("incomplete-plugin")
<< "incomplete-plugin/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: plugin directive requires one or two arguments, but 0 were provided")
<< QStringList()
<< QStringList()
@@ -250,6 +260,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("excessive-plugin")
<< "excessive-plugin/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: plugin directive requires one or two arguments, but 3 were provided")
<< QStringList()
<< QStringList()
@@ -260,6 +271,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("name-plugin")
<< "name-plugin/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "foo|")
<< QStringList()
@@ -270,6 +282,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("name-path-plugin")
<< "name-path-plugin/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "foo|bar")
<< QStringList()
@@ -280,6 +293,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("unversioned-component")
<< "unversioned-component/qmldir"
+ << QString()
<< QStringList()
<< QStringList()
<< QStringList()
@@ -290,6 +304,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("invalid-versioned-component")
<< "invalid-versioned-component/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: invalid version 100, expected <major>.<minor>")
<< QStringList()
<< QStringList()
@@ -300,6 +315,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("versioned-component")
<< "versioned-component/qmldir"
+ << QString()
<< QStringList()
<< QStringList()
<< QStringList()
@@ -310,6 +326,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("invalid-versioned-script")
<< "invalid-versioned-script/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: invalid version 100, expected <major>.<minor>")
<< QStringList()
<< QStringList()
@@ -320,6 +337,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("versioned-script")
<< "versioned-script/qmldir"
+ << QString()
<< QStringList()
<< QStringList()
<< QStringList()
@@ -330,6 +348,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("multiple")
<< "multiple/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "PluginA|plugina.so")
<< QStringList()
@@ -342,6 +361,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("designersupported-yes")
<< "designersupported-yes/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "foo|")
<< QStringList()
@@ -352,6 +372,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("designersupported-no")
<< "designersupported-no/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "foo|")
<< QStringList()
@@ -362,6 +383,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("invalid-versioned-dependency")
<< "invalid-versioned-dependency/qmldir"
+ << QString()
<< (QStringList() << "qmldir:1: invalid version 100, expected <major>.<minor>")
<< QStringList()
<< QStringList()
@@ -372,6 +394,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("dependency")
<< "dependency/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "foo|")
<< QStringList()
@@ -382,6 +405,7 @@ void tst_qqmldirparser::parse_data()
QTest::newRow("classname")
<< "classname/qmldir"
+ << QString()
<< QStringList()
<< (QStringList() << "qtquick2plugin|")
<< (QStringList() << "QtQuick2Plugin")
@@ -389,6 +413,39 @@ void tst_qqmldirparser::parse_data()
<< QStringList()
<< QStringList()
<< true;
+
+ QTest::newRow("prefer")
+ << "prefer/qmldir"
+ << ":/foo/bar/"
+ << QStringList()
+ << QStringList({"doesnotexist|"})
+ << QStringList({"FooBarInstance"})
+ << QStringList({"Foo|Foo.qml|1|0|false"})
+ << QStringList()
+ << QStringList()
+ << false;
+
+ QTest::newRow("two-prefer")
+ << "two-prefer/qmldir"
+ << ":/foo/bar/"
+ << QStringList({"qmldir:4: only one prefer directive may be defined in a qmldir file"})
+ << QStringList({"doesnotexist|"})
+ << QStringList({"FooBarInstance"})
+ << QStringList({"Foo|Foo.qml|1|0|false"})
+ << QStringList()
+ << QStringList()
+ << false;
+
+ QTest::newRow("prefer-no-slash")
+ << "prefer-no-slash/qmldir"
+ << QString()
+ << QStringList({"qmldir:3: the preferred directory has to end with a '/'"})
+ << QStringList({"doesnotexist|"})
+ << QStringList({"FooBarInstance"})
+ << QStringList({"Foo|Foo.qml|1|0|false"})
+ << QStringList()
+ << QStringList()
+ << false;
}
void tst_qqmldirparser::parse()
diff --git a/tests/auto/qml/qqmlimport/CMakeLists.txt b/tests/auto/qml/qqmlimport/CMakeLists.txt
index dba34c1a0b..3d4f01da2c 100644
--- a/tests/auto/qml/qqmlimport/CMakeLists.txt
+++ b/tests/auto/qml/qqmlimport/CMakeLists.txt
@@ -36,6 +36,13 @@ qt_internal_add_test(tst_qqmlimport
TESTDATA ${test_data}
)
+qt_internal_add_resource(tst_qqmlimport "preferred"
+ PREFIX
+ "/qqmlimport/ModuleWithPrefer/"
+ FILES
+ "Preferred.qml"
+)
+
## Scopes:
#####################################################################
diff --git a/tests/auto/qml/qqmlimport/Preferred.qml b/tests/auto/qml/qqmlimport/Preferred.qml
new file mode 100644
index 0000000000..8fda58451e
--- /dev/null
+++ b/tests/auto/qml/qqmlimport/Preferred.qml
@@ -0,0 +1,5 @@
+import QtQml
+
+QtObject {
+ objectName: "right"
+}
diff --git a/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/Preferred.qml b/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/Preferred.qml
new file mode 100644
index 0000000000..a4af255b56
--- /dev/null
+++ b/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/Preferred.qml
@@ -0,0 +1,5 @@
+import QtQml
+
+QtObject {
+ objectName: "wrong"
+}
diff --git a/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/qmldir b/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/qmldir
new file mode 100644
index 0000000000..99f2a28cd1
--- /dev/null
+++ b/tests/auto/qml/qqmlimport/data/ModuleWithPrefer/qmldir
@@ -0,0 +1,3 @@
+module ModuleWithPrefer
+prefer :/qqmlimport/ModuleWithPrefer/
+Preferred 1.0 Preferred.qml
diff --git a/tests/auto/qml/qqmlimport/data/prefer.qml b/tests/auto/qml/qqmlimport/data/prefer.qml
new file mode 100644
index 0000000000..7a6764e08f
--- /dev/null
+++ b/tests/auto/qml/qqmlimport/data/prefer.qml
@@ -0,0 +1,3 @@
+import ModuleWithPrefer
+
+Preferred {}
diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp
index df3d2ba625..4f2b4f6b13 100644
--- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp
+++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp
@@ -55,6 +55,7 @@ private slots:
void importDependenciesPrecedence();
void cleanup();
void envResourceImportPath();
+ void preferResourcePath();
};
void tst_QQmlImport::cleanup()
@@ -89,6 +90,17 @@ void tst_QQmlImport::envResourceImportPath()
QVERIFY((importPaths.contains(path.startsWith(u':') ? QLatin1String("qrc") + path : path)));
}
+void tst_QQmlImport::preferResourcePath()
+{
+ QQmlEngine engine;
+ engine.addImportPath(dataDirectory());
+
+ QQmlComponent component(&engine, testFileUrl("prefer.qml"));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> o(component.create());
+ QCOMPARE(o->objectName(), "right");
+}
+
void tst_QQmlImport::testDesignerSupported()
{
QQuickView *window = new QQuickView();