CMake: Always add the qmldir to the resource file system

We want to be able to load modules directly from the resource file
system, no matter if we build statically or dynamically. It's limited by
the need to load plugins, of course, but as most plugins are optional an
application can easily go this way and never include any plain QML files
in an application bundle.

Pick-to: 6.2
Change-Id: I94657a43b47e442722a83e1fb306680aa50b1bc3
Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io>
This commit is contained in:
Ulf Hermann 2021-06-22 19:48:02 +02:00
parent 37540467ca
commit 79d0517a12
9 changed files with 80 additions and 34 deletions

View File

@ -452,33 +452,30 @@ function(qt6_add_qml_module target)
if(NOT arg_NO_GENERATE_QMLDIR)
_qt_internal_target_generate_qmldir(${target} ${arg_OUTPUT_DIRECTORY} ${arg_TARGET_PATH})
get_target_property(target_type ${target} TYPE)
if(target_type STREQUAL "STATIC_LIBRARY")
# Embed qmldir in static builds. The following comments relate mostly to Qt5->6 transition.
# The requirement to keep the same resource name might no longer apply, but it doesn't
# currently appear to cause any hinderance to keep it.
# The qmldir resource name needs to match the one generated by qmake's qml_module.prf, to
# ensure that all Q_INIT_RESOURCE(resource_name) calls in Qt code don't lead to undefined
# symbol errors when linking an application project.
# The Q_INIT_RESOURCE() calls are not strictly necessary anymore because the CMake Qt
# build passes around the compiled resources as object files.
# These object files have global initiliazers that don't get discared when linked into
# an application (as opposed to when the resource libraries were embedded into the static
# libraries when Qt was built with qmake).
# The reason to match the naming is to ensure that applications link successfully regardless
# if Qt was built with CMake or qmake, while the build system transition phase is still
# happening.
string(REPLACE "/" "_" qmldir_resource_name "qmake_${arg_TARGET_PATH}")
set_source_files_properties(${arg_OUTPUT_DIRECTORY}/qmldir
PROPERTIES QT_RESOURCE_ALIAS "qmldir"
)
set(resource_targets)
qt6_add_resources(${target} ${qmldir_resource_name}
FILES ${arg_OUTPUT_DIRECTORY}/qmldir
OUTPUT_TARGETS resource_targets
)
list(APPEND output_targets ${resource_targets})
endif()
# Embed qmldir in qrc. The following comments relate mostly to Qt5->6 transition.
# The requirement to keep the same resource name might no longer apply, but it doesn't
# currently appear to cause any hinderance to keep it.
# The qmldir resource name needs to match the one generated by qmake's qml_module.prf, to
# ensure that all Q_INIT_RESOURCE(resource_name) calls in Qt code don't lead to undefined
# symbol errors when linking an application project.
# The Q_INIT_RESOURCE() calls are not strictly necessary anymore because the CMake Qt
# build passes around the compiled resources as object files.
# These object files have global initiliazers that don't get discared when linked into
# an application (as opposed to when the resource libraries were embedded into the static
# libraries when Qt was built with qmake).
# The reason to match the naming is to ensure that applications link successfully regardless
# if Qt was built with CMake or qmake, while the build system transition phase is still
# happening.
string(REPLACE "/" "_" qmldir_resource_name "qmake_${arg_TARGET_PATH}")
set_source_files_properties(${arg_OUTPUT_DIRECTORY}/qmldir
PROPERTIES QT_RESOURCE_ALIAS "qmldir"
)
set(resource_targets)
qt6_add_resources(${target} ${qmldir_resource_name}
FILES ${arg_OUTPUT_DIRECTORY}/qmldir
OUTPUT_TARGETS resource_targets
)
list(APPEND output_targets ${resource_targets})
endif()
if(arg_PLUGIN_TARGET AND NOT arg_NO_CREATE_PLUGIN_TARGET)

View File

@ -695,12 +695,15 @@ void QQmlTypeData::continueLoadFromIR()
for (const QV4::CompiledData::Import *import : qAsConst(m_document->imports)) {
if (!addImport(import, {}, &errors)) {
Q_ASSERT(errors.size());
for (QQmlError &error : errors) {
error.setUrl(m_importCache.baseUrl());
error.setLine(qmlConvertSourceCoordinate<quint32, int>(import->location.line));
error.setColumn(qmlConvertSourceCoordinate<quint32, int>(import->location.column));
}
setError(errors);
// We're only interested in the chronoligically last error. The previous
// errors might be from unsuccessfully trying to load a module from the
// resource file system.
QQmlError error = errors.first();
error.setUrl(m_importCache.baseUrl());
error.setLine(qmlConvertSourceCoordinate<quint32, int>(import->location.line));
error.setColumn(qmlConvertSourceCoordinate<quint32, int>(import->location.column));
setError(error);
return;
}
}

View File

@ -62,23 +62,32 @@ void tst_basicapp::loadComponent()
void tst_basicapp::resourceFiles()
{
QVERIFY(QFile::exists(QStringLiteral(":/BasicApp/main.qml")));
QVERIFY(QFile::exists(QStringLiteral(":/BasicApp/qmldir")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/Clock.qml")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/center.png")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/clock.png")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/hour.png")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/minute.png")));
QVERIFY(QFile::exists(QStringLiteral(":/TimeExample/qmldir")));
QVERIFY(!QFile::exists(QStringLiteral(":/BasicApp/tst_qmlbasicapp.qmltypes")));
QVERIFY(!QFile::exists(QStringLiteral(":/TimeExample/qmlqtimeexample.qmltypes")));
}
void tst_basicapp::fileSystemFiles()
{
const QString basedir = QCoreApplication::applicationDirPath();
QVERIFY(QFile::exists(basedir + QStringLiteral("/main.qml")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/qmldir")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/tst_qmlbasicapp.qmltypes")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/Clock.qml")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/center.png")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/clock.png")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/hour.png")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/minute.png")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/qmldir")));
QVERIFY(QFile::exists(basedir + QStringLiteral("/TimeExample/qmlqtimeexample.qmltypes")));
}
void tst_basicapp::qmldirContents()
@ -92,6 +101,10 @@ void tst_basicapp::qmldirContents()
QVERIFY(contents.contains("prefer :/BasicApp/"));
QVERIFY(!contents.contains("classname"));
QVERIFY(!contents.contains("plugin"));
QFile qmldirInResources(":/BasicApp/qmldir");
QVERIFY(qmldirInResources.open(QIODevice::ReadOnly));
QCOMPARE(qmldirInResources.readAll(), contents);
}
{
@ -105,6 +118,10 @@ void tst_basicapp::qmldirContents()
QVERIFY(contents.contains("depends QtQml"));
QVERIFY(contents.contains("prefer :/TimeExample/"));
QVERIFY(contents.contains("Clock 1.0 Clock.qml"));
QFile qmldirInResources(":/TimeExample/qmldir");
QVERIFY(qmldirInResources.open(QIODevice::ReadOnly));
QCOMPARE(qmldirInResources.readAll(), contents);
}
}

View File

@ -1,2 +1 @@
2:1:"org.qtproject.PureJsModule" is ambiguous.
2:1:"org.qtproject.PureJsModule" is ambiguous.

View File

@ -56,6 +56,18 @@ qt_internal_add_resource(tst_qqmlmoduleplugin "qmake_staticPlugin"
${qmake_staticPlugin_resource_files}
)
set_source_files_properties("imports/ModuleFromQrc/badqmldir"
PROPERTIES QT_RESOURCE_ALIAS "imports/ModuleFromQrc/qmldir"
)
qt_internal_add_resource(tst_qqmlmoduleplugin "moduleFromQrc"
PREFIX
"/foo/"
FILES
"imports/ModuleFromQrc/badqmldir"
"imports/ModuleFromQrc/Foo.qml"
)
#### Keys ignored in scope 2:.:.:tst_qqmlmoduleplugin.pro:<TRUE>:
# CONFIG = "testcase" "-app_bundle"

View File

@ -0,0 +1,2 @@
import QtQml
QtObject {}

View File

@ -0,0 +1,3 @@
module ModuleFromQrc
plugin badbad
Foo 1.0 Foo.qml

View File

@ -0,0 +1,2 @@
module ModuleFromQrc
Foo 1.0 Foo.qml

View File

@ -86,6 +86,7 @@ private slots:
void parallelPluginImport();
void multiSingleton();
void optionalPlugin();
void moduleFromQrc();
private:
QString m_importsDirectory;
@ -830,6 +831,16 @@ void tst_qqmlmoduleplugin::optionalPlugin()
QVERIFY(!object10.isNull());
}
void tst_qqmlmoduleplugin::moduleFromQrc()
{
QQmlEngine engine;
engine.setImportPathList({ QStringLiteral(":/foo/imports/"), m_dataImportsDirectory });
QQmlComponent component(&engine);
component.setData("import ModuleFromQrc\nFoo {}\n", QUrl());
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(!object.isNull());
}
QTEST_MAIN(tst_qqmlmoduleplugin)