Make it easier to use resources in plugins when using static linking

RCC generates code that registers resources automatically on program
startup via global constructors. When linking statically and nothing
references the symbols in the .o file compiled from the RCC generated
code, then the linker will discard the embedded resources and they will
not get initialized. That is why for static linking it is necessary to
explicitly initialize resources using the Q_INIT_RESOURCE macro.

We can avoid the need for the explicit initialization in the context of
plugins that are statically linked into the application. resources.prf
can generate a .cpp file with a helper function that contains all the
Q_INIT_RESOURCE calls for all resources in the plugin. That helper
function in turn is injected into the plugin entry point, which in turn
is guaranteed to be included in the final binary.

Change-Id: If1abf9c85ef92935020af073b989c58c1ae6ca63
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
bb10
Simon Hausmann 2017-05-26 14:36:22 +02:00
parent 72bb1d95fd
commit be9a56e5e3
9 changed files with 107 additions and 24 deletions

View File

@ -72,6 +72,38 @@ for(resource, RESOURCES) {
RESOURCES += $$resource_file
}
!isEmpty(RESOURCES):contains(TEMPLATE, .*lib):plugin:static {
resource_init_function = $$lower($$basename(TARGET))_plugin_resource_init
DEFINES += "QT_PLUGIN_RESOURCE_INIT_FUNCTION=$$resource_init_function"
RESOURCE_INIT_CPP = $$OUT_PWD/$$lower($$basename(TARGET))_plugin_resources.cpp
GENERATED_SOURCES += $$RESOURCE_INIT_CPP
QMAKE_DISTCLEAN += $$RESOURCE_INIT_CPP
!build_pass {
RESOURCE_INIT_CONT = \
"// This file is autogenerated by qmake. It contains a function that" \
"// references all resources the plugin includes and the function is" \
"// referenced by Qt_(MOC_)EXPORT_PLUGIN to ensure the inclusion in" \
"// the statically linked plugin." \
"$${LITERAL_HASH}include <QtCore/qglobal.h>" \
"void $${resource_init_function}() " \
"{" \
for (resource, RESOURCES) {
resource_name = $$section($$list($$basename(resource)), ., 0, 0)
resource_name = $$replace(resource_name, [^a-zA-Z0-9_], _)
RESOURCE_INIT_CONT += " Q_INIT_RESOURCE($$resource_name);"
}
RESOURCE_INIT_CONT += \
"}"
write_file($$RESOURCE_INIT_CPP, RESOURCE_INIT_CONT)|error()
}
}
rcc.input = RESOURCES
rcc.name = RCC ${QMAKE_FILE_IN}
rcc.depend_command = $$QMAKE_RCC_DEP -list $$QMAKE_RESOURCE_FLAGS ${QMAKE_FILE_IN}

View File

@ -105,11 +105,21 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin);
}; \
static Static##PLUGIN##PluginInstance static##PLUGIN##Instance;
#if defined(QT_PLUGIN_RESOURCE_INIT_FUNCTION)
# define QT_PLUGIN_RESOURCE_INIT \
extern void QT_PLUGIN_RESOURCE_INIT_FUNCTION(); \
QT_PLUGIN_RESOURCE_INIT_FUNCTION();
#else
# define QT_PLUGIN_RESOURCE_INIT
#endif
#define Q_PLUGIN_INSTANCE(IMPLEMENTATION) \
{ \
static QT_PREPEND_NAMESPACE(QPointer)<QT_PREPEND_NAMESPACE(QObject)> _instance; \
if (!_instance) \
if (!_instance) { \
QT_PLUGIN_RESOURCE_INIT \
_instance = new IMPLEMENTATION; \
} \
return _instance; \
}

View File

@ -1,23 +1,2 @@
CONFIG += testcase
TARGET = tst_qresourceengine
QT = core testlib
SOURCES = tst_qresourceengine.cpp
RESOURCES += testqrc/test.qrc
qtPrepareTool(QMAKE_RCC, rcc, _DEP)
runtime_resource.target = runtime_resource.rcc
runtime_resource.depends = $$PWD/testqrc/test.qrc $$QMAKE_RCC_EXE
runtime_resource.commands = $$QMAKE_RCC -root /runtime_resource/ -binary $$PWD/testqrc/test.qrc -o $${runtime_resource.target}
QMAKE_EXTRA_TARGETS = runtime_resource
PRE_TARGETDEPS += $${runtime_resource.target}
QMAKE_DISTCLEAN += $${runtime_resource.target}
TESTDATA += \
parentdir.txt \
testqrc/*
GENERATED_TESTDATA = $${runtime_resource.target}
android:!android-embedded {
RESOURCES += android_testdata.qrc
}
TEMPLATE = subdirs
SUBDIRS = staticplugin qresourceengine_test.pro

View File

@ -0,0 +1,31 @@
CONFIG += testcase
TARGET = tst_qresourceengine
QT = core testlib
SOURCES = tst_qresourceengine.cpp
RESOURCES += testqrc/test.qrc
qtPrepareTool(QMAKE_RCC, rcc, _DEP)
runtime_resource.target = runtime_resource.rcc
runtime_resource.depends = $$PWD/testqrc/test.qrc $$QMAKE_RCC_EXE
runtime_resource.commands = $$QMAKE_RCC -root /runtime_resource/ -binary $$PWD/testqrc/test.qrc -o $${runtime_resource.target}
QMAKE_EXTRA_TARGETS = runtime_resource
PRE_TARGETDEPS += $${runtime_resource.target}
QMAKE_DISTCLEAN += $${runtime_resource.target}
TESTDATA += \
parentdir.txt \
testqrc/*
GENERATED_TESTDATA = $${runtime_resource.target}
android:!android-embedded {
RESOURCES += android_testdata.qrc
}
win32 {
CONFIG(debug, debug|release): LIBS += -Lstaticplugin/debug
else: LIBS += -Lstaticplugin/release
} else {
LIBS += -Lstaticplugin
}
LIBS += -lmoctestplugin

View File

@ -0,0 +1 @@
moctestplugin_plugin_resources.cpp

View File

@ -0,0 +1,9 @@
#include <QObject>
class PluginClass : public QObject
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.tests.moc" FILE "staticplugin.json")
};
#include "main.moc"

View File

@ -0,0 +1 @@
{ "Keys": [ "staticplugin" ] }

View File

@ -0,0 +1,8 @@
TEMPLATE = lib
TARGET = moctestplugin
CONFIG += plugin static
SOURCES = main.cpp
plugin_resource.files = main.cpp
plugin_resource.prefix = /staticplugin
RESOURCES += plugin_resource
QT = core

View File

@ -56,6 +56,7 @@ private slots:
void doubleSlashInRoot();
void setLocale();
void lastModified();
void resourcesInStaticPlugins();
private:
const QString m_runtimeResourceRcc;
@ -118,6 +119,7 @@ void tst_QResourceEngine::checkStructure_data()
<< QLatin1String("searchpath1")
<< QLatin1String("searchpath2")
<< QLatin1String("secondary_root")
<< QLatin1String("staticplugin")
<< QLatin1String("test")
<< QLatin1String("withoutslashes");
@ -504,6 +506,16 @@ void tst_QResourceEngine::lastModified()
}
}
Q_IMPORT_PLUGIN(PluginClass)
void tst_QResourceEngine::resourcesInStaticPlugins()
{
// We built a separate static plugin and attempted linking against
// it. That should successfully register the resources linked into
// the plugin via moc generated Q_INIT_RESOURCE calls in a
// Q_CONSTRUCTOR_FUNCTION.
QVERIFY(QFile::exists(":/staticplugin/main.cpp"));
}
QTEST_MAIN(tst_QResourceEngine)
#include "tst_qresourceengine.moc"