QPluginLoader: fix loading of plugins with a relative file name
authorDavid Faure <faure+bluesystems@kde.org>
Mon, 3 Dec 2012 11:29:10 +0000 (12:29 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 4 Dec 2012 17:38:41 +0000 (18:38 +0100)
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>
src/corelib/plugin/qlibrary_p.h
src/corelib/plugin/qlibrary_unix.cpp
src/corelib/plugin/qlibrary_win.cpp
src/corelib/plugin/qpluginloader.cpp
tests/auto/corelib/plugin/qpluginloader/almostplugin/almostplugin.pro
tests/auto/corelib/plugin/qpluginloader/theplugin/theplugin.pro
tests/auto/corelib/plugin/qpluginloader/tst/tst.pro
tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp

index 337c43a..8465d5c 100644 (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();
 
index 2ef6f80..35d8197 100644 (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)
index 27796ae..ef3816a 100644 (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
index 31d9fda..c16a340 100644 (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;
index 0004a8c..70ab54a 100644 (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.
index b510f8f..2ea9c27 100644 (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
index 48650a5..a7a9661 100644 (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) {
index 155267f..34ec66f 100644 (file)
 # 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)
 #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;