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 *);
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();

View File

@ -80,6 +80,60 @@ static QString qdlerror()
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()
{
QString attempt;
@ -96,50 +150,8 @@ bool QLibraryPrivate::load_sys()
QStringList suffixes;
QStringList prefixes;
if (pluginState != IsAPlugin) {
prefixes << QLatin1String("lib");
#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
prefixes = prefixes_sys();
suffixes = suffixes_sys(fullVersion);
}
int dlFlags = 0;
#if defined(QT_HPUX_LD)

View File

@ -57,6 +57,17 @@ QT_BEGIN_NAMESPACE
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()
{
//avoid 'Bad Image' message box

View File

@ -42,6 +42,7 @@
#include "qplatformdefs.h"
#include "qplugin.h"
#include "qcoreapplication.h"
#include "qpluginloader.h"
#include <qfileinfo.h>
#include "qlibrary_p.h"
@ -250,14 +251,52 @@ bool QPluginLoader::isLoaded() const
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
\brief the file name of the plugin
To be loadable, the file's suffix must be a valid suffix for a
loadable library in accordance with the platform, e.g. \c .so on
Unix, \c .dylib on Mac OS X, and \c .dll on Windows. The suffix
can be verified with QLibrary::isLibrary().
We recommend omitting the file's suffix in the file name, since
QPluginLoader will automatically look for the file with the appropriate
suffix (see 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
will then contain an empty string.
@ -277,7 +316,12 @@ void QPluginLoader::setFileName(const QString &fileName)
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->loadHints = lh;

View File

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

View File

@ -2,8 +2,11 @@ TEMPLATE = lib
CONFIG += plugin
HEADERS = theplugin.h
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
QT = core
# This is testdata for the tst_qpluginloader test.
target.path = $$[QT_INSTALL_TESTS]/tst_qpluginloader/bin

View File

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

View File

@ -59,7 +59,11 @@
# define bundle_VALID true
# define dylib_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"
#elif defined(Q_OS_HPUX) && !defined(__ia64)
@ -79,11 +83,11 @@
#elif defined(Q_OS_WIN)
# undef dll_VALID
# define dll_VALID true
# ifdef QT_NO_DEBUG
//# ifdef QT_NO_DEBUG
# define SUFFIX ".dll"
# else
# define SUFFIX "d.dll"
# endif
//# else
//# define SUFFIX "d.dll"
//# endif
# define PREFIX ""
#else // all other Unix
@ -111,6 +115,7 @@ private slots:
#if defined (Q_OS_UNIX)
void loadGarbage();
#endif
void relativePath();
void reloadPlugin();
};
@ -294,6 +299,20 @@ void tst_QPluginLoader::loadGarbage()
}
#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()
{
QPluginLoader loader;