QPluginLoader: fix loading of plugins with a relative file name

This makes QT_PLUGIN_PATH / QCoreApplication::libraryPaths() actually work,
as a search path for plugins, when apps look for a specific plugin by name.

To make it possible to write portable code (unlike the current QPluginLoader
unittest), let QPluginLoader figure out the extension, too.

Change-Id: I895d597d7cb05ded268734bc5f313f32d8d12cb9
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
David Faure 2012-12-03 12:29:10 +01:00 committed by The Qt Project
parent f6cc1f3aea
commit 418890e074
8 changed files with 148 additions and 55 deletions

View File

@ -92,6 +92,8 @@ public:
QFunctionPointer resolve(const char *); QFunctionPointer resolve(const char *);
static QLibraryPrivate *findOrCreate(const QString &fileName, const QString &version = QString()); static QLibraryPrivate *findOrCreate(const QString &fileName, const QString &version = QString());
static QStringList suffixes_sys(const QString &fullVersion);
static QStringList prefixes_sys();
static QVector<QStaticPlugin> staticPlugins(); static QVector<QStaticPlugin> staticPlugins();

View File

@ -80,6 +80,60 @@ static QString qdlerror()
return err ? QLatin1Char('(') + QString::fromLocal8Bit(err) + QLatin1Char(')'): QString(); return err ? QLatin1Char('(') + QString::fromLocal8Bit(err) + QLatin1Char(')'): QString();
} }
QStringList QLibraryPrivate::suffixes_sys(const QString& fullVersion)
{
QStringList suffixes;
#if defined(Q_OS_HPUX)
// according to
// http://docs.hp.com/en/B2355-90968/linkerdifferencesiapa.htm
// In PA-RISC (PA-32 and PA-64) shared libraries are suffixed
// with .sl. In IPF (32-bit and 64-bit), the shared libraries
// are suffixed with .so. For compatibility, the IPF linker
// also supports the .sl suffix.
// But since we don't know if we are built on HPUX or HPUXi,
// we support both .sl (and .<version>) and .so suffixes but
// .so is preferred.
# if defined(__ia64)
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".so");
}
# endif
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".sl.%1").arg(fullVersion);
suffixes << QString::fromLatin1(".%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".sl");
}
#elif defined(Q_OS_AIX)
suffixes << ".a";
#else
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".so");
}
#endif
# ifdef Q_OS_MAC
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".%1.bundle").arg(fullVersion);
suffixes << QString::fromLatin1(".%1.dylib").arg(fullVersion);
} else {
suffixes << QLatin1String(".bundle") << QLatin1String(".dylib");
}
#endif
return suffixes;
}
QStringList QLibraryPrivate::prefixes_sys()
{
return QStringList() << QLatin1String("lib");
}
bool QLibraryPrivate::load_sys() bool QLibraryPrivate::load_sys()
{ {
QString attempt; QString attempt;
@ -96,50 +150,8 @@ bool QLibraryPrivate::load_sys()
QStringList suffixes; QStringList suffixes;
QStringList prefixes; QStringList prefixes;
if (pluginState != IsAPlugin) { if (pluginState != IsAPlugin) {
prefixes << QLatin1String("lib"); prefixes = prefixes_sys();
#if defined(Q_OS_HPUX) suffixes = suffixes_sys(fullVersion);
// according to
// http://docs.hp.com/en/B2355-90968/linkerdifferencesiapa.htm
// In PA-RISC (PA-32 and PA-64) shared libraries are suffixed
// with .sl. In IPF (32-bit and 64-bit), the shared libraries
// are suffixed with .so. For compatibility, the IPF linker
// also supports the .sl suffix.
// But since we don't know if we are built on HPUX or HPUXi,
// we support both .sl (and .<version>) and .so suffixes but
// .so is preferred.
# if defined(__ia64)
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".so");
}
# endif
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".sl.%1").arg(fullVersion);
suffixes << QString::fromLatin1(".%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".sl");
}
#elif defined(Q_OS_AIX)
suffixes << ".a";
#else
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
} else {
suffixes << QLatin1String(".so");
}
#endif
# ifdef Q_OS_MAC
if (!fullVersion.isEmpty()) {
suffixes << QString::fromLatin1(".%1.bundle").arg(fullVersion);
suffixes << QString::fromLatin1(".%1.dylib").arg(fullVersion);
} else {
suffixes << QLatin1String(".bundle") << QLatin1String(".dylib");
}
#endif
} }
int dlFlags = 0; int dlFlags = 0;
#if defined(QT_HPUX_LD) #if defined(QT_HPUX_LD)

View File

@ -57,6 +57,17 @@ QT_BEGIN_NAMESPACE
extern QString qt_error_string(int code); extern QString qt_error_string(int code);
QStringList QLibraryPrivate::suffixes_sys(const QString& fullVersion)
{
Q_UNUSED(fullVersion);
return QStringList() << ".dll";
}
QStringList QLibraryPrivate::prefixes_sys()
{
return QStringList();
}
bool QLibraryPrivate::load_sys() bool QLibraryPrivate::load_sys()
{ {
//avoid 'Bad Image' message box //avoid 'Bad Image' message box

View File

@ -42,6 +42,7 @@
#include "qplatformdefs.h" #include "qplatformdefs.h"
#include "qplugin.h" #include "qplugin.h"
#include "qcoreapplication.h"
#include "qpluginloader.h" #include "qpluginloader.h"
#include <qfileinfo.h> #include <qfileinfo.h>
#include "qlibrary_p.h" #include "qlibrary_p.h"
@ -250,14 +251,52 @@ bool QPluginLoader::isLoaded() const
return d && d->pHnd && d->instance; return d && d->pHnd && d->instance;
} }
static QString locatePlugin(const QString& fileName)
{
QStringList prefixes = QLibraryPrivate::prefixes_sys();
prefixes.prepend(QString());
QStringList suffixes = QLibraryPrivate::suffixes_sys(QString());
suffixes.prepend(QString());
// Split up "subdir/filename"
const int slash = fileName.lastIndexOf('/');
const QString baseName = fileName.mid(slash + 1);
const QString basePath = fileName.left(slash + 1); // keep the '/'
const bool debug = qt_debug_component();
QStringList paths = QCoreApplication::libraryPaths();
paths.prepend(QStringLiteral("./")); // search in current dir first
foreach (const QString &path, paths) {
foreach (const QString &prefix, prefixes) {
foreach (const QString &suffix, suffixes) {
const QString fn = path + QLatin1Char('/') + basePath + prefix + baseName + suffix;
if (debug)
qDebug() << "Trying..." << fn;
if (QFileInfo(fn).isFile())
return fn;
}
}
}
if (debug)
qDebug() << fileName << "not found";
return QString();
}
/*! /*!
\property QPluginLoader::fileName \property QPluginLoader::fileName
\brief the file name of the plugin \brief the file name of the plugin
To be loadable, the file's suffix must be a valid suffix for a We recommend omitting the file's suffix in the file name, since
loadable library in accordance with the platform, e.g. \c .so on QPluginLoader will automatically look for the file with the appropriate
Unix, \c .dylib on Mac OS X, and \c .dll on Windows. The suffix suffix (see QLibrary::isLibrary()).
can be verified with QLibrary::isLibrary().
When loading the plugin, QPluginLoader searches in the current directory and
in all plugin locations specified by QCoreApplication::libraryPaths(),
unless the file name has an absolute path. After loading the plugin
successfully, fileName() returns the fully-qualified file name of
the plugin, including the full path to the plugin if one was given
in the constructor or passed to setFileName().
If the file name does not exist, it will not be set. This property If the file name does not exist, it will not be set. This property
will then contain an empty string. will then contain an empty string.
@ -277,7 +316,12 @@ void QPluginLoader::setFileName(const QString &fileName)
did_load = false; did_load = false;
} }
QString fn = QFileInfo(fileName).canonicalFilePath(); QFileInfo fi(fileName);
QString fn;
if (fi.isAbsolute())
fn = fi.canonicalFilePath();
else
fn = locatePlugin(fileName);
d = QLibraryPrivate::findOrCreate(fn); d = QLibraryPrivate::findOrCreate(fn);
d->loadHints = lh; d->loadHints = lh;

View File

@ -4,6 +4,7 @@ HEADERS = almostplugin.h
SOURCES = almostplugin.cpp SOURCES = almostplugin.cpp
TARGET = almostplugin TARGET = almostplugin
DESTDIR = ../bin DESTDIR = ../bin
QT = core
*-g++*:QMAKE_LFLAGS -= -Wl,--no-undefined *-g++*:QMAKE_LFLAGS -= -Wl,--no-undefined
# This is testdata for the tst_qpluginloader test. # This is testdata for the tst_qpluginloader test.

View File

@ -2,8 +2,11 @@ TEMPLATE = lib
CONFIG += plugin CONFIG += plugin
HEADERS = theplugin.h HEADERS = theplugin.h
SOURCES = theplugin.cpp SOURCES = theplugin.cpp
TARGET = $$qtLibraryTarget(theplugin) # Use a predictable name for the plugin, no debug extension. Just like most apps do.
#TARGET = $$qtLibraryTarget(theplugin)
TARGET = theplugin
DESTDIR = ../bin DESTDIR = ../bin
QT = core
# This is testdata for the tst_qpluginloader test. # This is testdata for the tst_qpluginloader test.
target.path = $$[QT_INSTALL_TESTS]/tst_qpluginloader/bin target.path = $$[QT_INSTALL_TESTS]/tst_qpluginloader/bin

View File

@ -4,6 +4,7 @@ TARGET = ../tst_qpluginloader
QT = core testlib QT = core testlib
SOURCES = ../tst_qpluginloader.cpp SOURCES = ../tst_qpluginloader.cpp
HEADERS = ../theplugin/plugininterface.h HEADERS = ../theplugin/plugininterface.h
CONFIG -= app_bundle
win32 { win32 {
CONFIG(debug, debug|release) { CONFIG(debug, debug|release) {

View File

@ -59,7 +59,11 @@
# define bundle_VALID true # define bundle_VALID true
# define dylib_VALID true # define dylib_VALID true
# define so_VALID true # define so_VALID true
# define SUFFIX ".dylib" //# ifdef QT_NO_DEBUG
# define SUFFIX ".dylib"
//# else
//# define SUFFIX "_debug.dylib"
//#endif
# define PREFIX "lib" # define PREFIX "lib"
#elif defined(Q_OS_HPUX) && !defined(__ia64) #elif defined(Q_OS_HPUX) && !defined(__ia64)
@ -79,11 +83,11 @@
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
# undef dll_VALID # undef dll_VALID
# define dll_VALID true # define dll_VALID true
# ifdef QT_NO_DEBUG //# ifdef QT_NO_DEBUG
# define SUFFIX ".dll" # define SUFFIX ".dll"
# else //# else
# define SUFFIX "d.dll" //# define SUFFIX "d.dll"
# endif //# endif
# define PREFIX "" # define PREFIX ""
#else // all other Unix #else // all other Unix
@ -111,6 +115,7 @@ private slots:
#if defined (Q_OS_UNIX) #if defined (Q_OS_UNIX)
void loadGarbage(); void loadGarbage();
#endif #endif
void relativePath();
void reloadPlugin(); void reloadPlugin();
}; };
@ -294,6 +299,20 @@ void tst_QPluginLoader::loadGarbage()
} }
#endif #endif
void tst_QPluginLoader::relativePath()
{
// Windows binaries run from release and debug subdirs, so we can't rely on the current dir.
const QString binDir = QFINDTESTDATA("bin");
QVERIFY(!binDir.isEmpty());
QCoreApplication::addLibraryPath(binDir);
QPluginLoader loader("theplugin");
loader.load(); // not recommended, instance() should do the job.
PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance());
QVERIFY(instance);
QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
QVERIFY(loader.unload());
}
void tst_QPluginLoader::reloadPlugin() void tst_QPluginLoader::reloadPlugin()
{ {
QPluginLoader loader; QPluginLoader loader;