changeset 1251:67aee57e32c8 3.0-integration

Merge from branch piper
author Chris Cannam
date Fri, 04 Nov 2016 14:57:03 +0000
parents c6bdf247016a (current diff) c2d66e3c83d0 (diff)
children 2ff5e411151d
files
diffstat 13 files changed, 477 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/base/Debug.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/base/Debug.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -21,7 +21,7 @@
 #include <QUrl>
 #include <QCoreApplication>
 
-#ifndef NDEBUG
+#include <stdexcept>
 
 static SVDebug *debug = 0;
 static QMutex mutex;
@@ -40,6 +40,11 @@
     m_ok(false),
     m_eol(false)
 {
+    if (qApp->applicationName() == "") {
+        cerr << "ERROR: Can't use SVDEBUG before setting application name" << endl;
+        throw std::logic_error("Can't use SVDEBUG before setting application name");
+    }
+    
     QString pfx = ResourceFinder().getUserResourcePrefix();
     QDir logdir(QString("%1/%2").arg(pfx).arg("log"));
 
@@ -76,8 +81,6 @@
     return dbg;
 }
 
-#endif
-
 std::ostream &
 operator<<(std::ostream &target, const QString &str)
 {
--- a/base/Debug.h	Tue Nov 01 14:06:47 2016 +0000
+++ b/base/Debug.h	Fri Nov 04 14:57:03 2016 +0000
@@ -36,8 +36,6 @@
 using std::cerr;
 using std::endl;
 
-#ifndef NDEBUG
-
 class SVDebug {
 public:
     SVDebug();
@@ -72,23 +70,5 @@
 
 #define SVDEBUG getSVDebug()
 
-#else
-
-class NoDebug
-{
-public:
-    inline NoDebug() {}
-    inline ~NoDebug(){}
-
-    template <typename T>
-    inline NoDebug &operator<<(const T &) { return *this; }
-
-    inline NoDebug &operator<<(QTextStreamFunction) { return *this; }
-};
-
-#define SVDEBUG NoDebug()
-
-#endif /* !NDEBUG */
-
 #endif /* !_DEBUG_H_ */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/HelperExecPath.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -0,0 +1,105 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "HelperExecPath.h"
+
+#include <QCoreApplication>
+#include <QFile>
+#include <QDir>
+#include <QFileInfo>
+
+QStringList
+HelperExecPath::getTags()
+{
+    if (sizeof(void *) == 8) {
+        if (m_type == NativeArchitectureOnly) {
+            return { "64", "" };
+        } else {
+            return { "64", "", "32" };
+        }
+    } else {
+        return { "", "32" };
+    }
+}
+
+static bool
+isGood(QString path)
+{
+    return QFile(path).exists() && QFileInfo(path).isExecutable();
+}
+
+QList<HelperExecPath::HelperExec>
+HelperExecPath::getHelperExecutables(QString basename)
+{
+    QStringList dummy;
+    return search(basename, dummy);
+}
+
+QString
+HelperExecPath::getHelperExecutable(QString basename)
+{
+    auto execs = getHelperExecutables(basename);
+    if (execs.empty()) return "";
+    else return execs[0].executable;
+}
+
+QStringList
+HelperExecPath::getHelperDirPaths()
+{
+    QStringList dirs;
+    QString myDir = QCoreApplication::applicationDirPath();
+    dirs.push_back(myDir + "/helpers");
+    dirs.push_back(myDir);
+    return dirs;
+}
+
+QStringList
+HelperExecPath::getHelperCandidatePaths(QString basename)
+{
+    QStringList candidates;
+    (void)search(basename, candidates);
+    return candidates;
+}
+
+QList<HelperExecPath::HelperExec>
+HelperExecPath::search(QString basename, QStringList &candidates)
+{
+    // Helpers are expected to exist either in the same directory as
+    // this executable was found, or in a subdirectory called helpers.
+
+    QString extension = "";
+#ifdef _WIN32
+    extension = ".exe";
+#endif
+
+    QList<HelperExec> executables;
+    QStringList dirs = getHelperDirPaths();
+    
+    for (QString t: getTags()) {
+        for (QString d: dirs) {
+            QString path = d + QDir::separator() + basename;
+            if (t != QString()) path += "-" + t;
+            path += extension;
+            candidates.push_back(path);
+            if (isGood(path)) {
+                executables.push_back({ path, t });
+                break;
+            }
+        }
+    }
+
+    return executables;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/HelperExecPath.h	Fri Nov 04 14:57:03 2016 +0000
@@ -0,0 +1,84 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_HELPER_EXEC_PATH_H
+#define SV_HELPER_EXEC_PATH_H
+
+#include <QStringList>
+
+/**
+ * Class to find helper executables that have been installed alongside
+ * the application. There may be more than one executable available
+ * with a given base name, because it's possible to have more than one
+ * implementation of a given service. For example, a plugin helper or
+ * scanner may exist in both 32-bit and 64-bit variants.
+ *
+ * This class encodes both the expected locations of helper
+ * executables, and the expected priority between different
+ * implementations (e.g. preferring the architecture that matches that
+ * of the host).
+ */
+class HelperExecPath
+{
+public:
+    enum SearchType {
+        NativeArchitectureOnly,
+        AllInstalled
+    };
+    
+    HelperExecPath(SearchType type) : m_type(type) { }
+    
+    /**
+     * Find a helper executable with the given base name in the bundle
+     * directory or installation location, if one exists, and return
+     * its full path. Equivalent to calling getHelperExecutables() and
+     * taking the first result from the returned list (or "" if empty).
+     */
+    QString getHelperExecutable(QString basename);
+
+    struct HelperExec {
+        QString executable;
+        QString tag;
+    };
+    
+    /**
+     * Find all helper executables with the given base name in the
+     * bundle directory or installation location, and return their
+     * full paths in order of priority. The "tag" string contains an
+     * identifier for the location or architecture of the helper, for
+     * example "32", "64", "js" etc. An empty tag signifies a default
+     * helper that matches the application's architecture.
+     */
+    QList<HelperExec> getHelperExecutables(QString basename);
+
+    /**
+     * Return the list of directories searched for helper
+     * executables.
+     */
+    QStringList getHelperDirPaths();
+    
+    /**
+     * Return the list of executable paths examined in the search for
+     * the helper executable with the given basename.
+     */
+    QStringList getHelperCandidatePaths(QString basename);
+
+private:
+    SearchType m_type;
+    QList<HelperExec> search(QString, QStringList &);
+    QStringList getTags();
+};
+
+#endif
--- a/base/ResourceFinder.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/base/ResourceFinder.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -33,6 +33,7 @@
 
 #include <cstdlib>
 #include <iostream>
+#include <stdexcept>
 
 /**
    Resource files may be found in three places:
@@ -126,6 +127,11 @@
 static QString
 getNewStyleUserResourcePrefix()
 {
+    if (qApp->applicationName() == "") {
+        cerr << "ERROR: Can't use ResourceFinder before setting application name" << endl;
+        throw std::logic_error("Can't use ResourceFinder before setting application name");
+    }
+
 #if QT_VERSION >= 0x050000
     // This is expected to be much more reliable than
     // getOldStyleUserResourcePrefix(), but it returns a different
@@ -133,7 +139,7 @@
     // fair enough). Hence migrateOldStyleResources() which moves
     // across any resources found in the old-style path the first time
     // we look for the new-style one
-    return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
+    return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
 #else
     return getOldStyleUserResourcePrefix();
 #endif
--- a/plugin/FeatureExtractionPluginFactory.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -20,6 +20,7 @@
 #include <QMutexLocker>
 
 #include "base/Preferences.h"
+#include "base/Debug.h"
 
 FeatureExtractionPluginFactory *
 FeatureExtractionPluginFactory::instance()
@@ -31,13 +32,21 @@
     
     if (!instance) {
 
+#ifdef HAVE_PIPER
         if (Preferences::getInstance()->getRunPluginsInProcess()) {
-            cerr << "creating native instance" << endl;
+            SVDEBUG << "FeatureExtractionPluginFactory: creating native instance"
+                    << endl;
             instance = new NativeVampPluginFactory();
         } else {
-            cerr << "creating piper instance" << endl;
+            SVDEBUG << "FeatureExtractionPluginFactory: creating Piper instance"
+                    << endl;
             instance = new PiperVampPluginFactory();
         }
+#else
+        SVDEBUG << "FeatureExtractionPluginFactory: no Piper support enabled,"
+                << " creating native instance" << endl;
+        instance = new NativeVampPluginFactory();
+#endif
     }
 
     return instance;
--- a/plugin/LADSPAPluginFactory.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/LADSPAPluginFactory.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -668,11 +668,11 @@
 
     generateFallbackCategories();
 
-    QStringList candidates =
+    auto candidates =
         PluginScan::getInstance()->getCandidateLibrariesFor(getPluginType());
 
-    for (QString c: candidates) {
-        discoverPluginsFrom(c);
+    for (auto c: candidates) {
+        discoverPluginsFrom(c.libraryPath);
     }
 }
 
--- a/plugin/NativeVampPluginFactory.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/NativeVampPluginFactory.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -70,6 +70,40 @@
     return m_pluginPath;
 }
 
+static
+QList<PluginScan::Candidate>
+getCandidateLibraries()
+{
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+    return PluginScan::getInstance()->getCandidateLibrariesFor
+        (PluginScan::VampPlugin);
+#else
+    auto path = Vamp::PluginHostAdapter::getPluginPath();
+    QList<PluginScan::Candidate> candidates;
+    for (string dirname: path) {
+        SVDEBUG << "NativeVampPluginFactory: scanning directory myself: "
+                << dirname << endl;
+#if defined(_WIN32)
+#define PLUGIN_GLOB "*.dll"
+#elif defined(__APPLE__)
+#define PLUGIN_GLOB "*.dylib *.so"
+#else
+#define PLUGIN_GLOB "*.so"
+#endif
+        QDir dir(dirname.c_str(), PLUGIN_GLOB,
+                 QDir::Name | QDir::IgnoreCase,
+                 QDir::Files | QDir::Readable);
+
+        for (unsigned int i = 0; i < dir.count(); ++i) {
+            QString soname = dir.filePath(dir[i]);
+            candidates.push_back({ soname, "" });
+        }
+    }
+
+    return candidates;
+#endif
+}
+
 vector<QString>
 NativeVampPluginFactory::getPluginIdentifiers(QString &)
 {
@@ -80,16 +114,21 @@
     if (!m_identifiers.empty()) {
         return m_identifiers;
     }
+
+    auto candidates = getCandidateLibraries();
     
-    QStringList candidates = PluginScan::getInstance()->getCandidateLibrariesFor
-        (PluginScan::VampPlugin);
-    
-    for (QString soname : candidates) {
+    SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl;
+        
+    for (auto candidate : candidates) {
 
+        QString soname = candidate.libraryPath;
+
+        SVDEBUG << "INFO: Considering candidate Vamp plugin library " << soname << endl;
+        
         void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
             
         if (!libraryHandle) {
-            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
             continue;
         }
 
@@ -97,9 +136,9 @@
             DLSYM(libraryHandle, "vampGetPluginDescriptor");
 
         if (!fn) {
-            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
             if (DLCLOSE(libraryHandle) != 0) {
-                cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+                SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
             }
             continue;
         }
@@ -117,13 +156,13 @@
         while ((descriptor = fn(VAMP_API_VERSION, index))) {
 
             if (known.find(descriptor->identifier) != known.end()) {
-                cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
+                SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
                      << soname
                      << " returns the same plugin identifier \""
                      << descriptor->identifier << "\" at indices "
                      << known[descriptor->identifier] << " and "
                      << index << endl;
-                    cerr << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
+                    SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
                 ok = false;
                 break;
             } else {
@@ -150,7 +189,7 @@
         }
             
         if (DLCLOSE(libraryHandle) != 0) {
-            cerr << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
         }
     }
 
@@ -271,7 +310,7 @@
     QString found = findPluginFile(soname);
 
     if (found == "") {
-        cerr << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
+        SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
         return 0;
     } else if (found != soname) {
 
@@ -287,7 +326,7 @@
     void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
             
     if (!libraryHandle) {
-        cerr << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
+        SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
         return 0;
     }
 
@@ -295,7 +334,7 @@
         DLSYM(libraryHandle, "vampGetPluginDescriptor");
     
     if (!fn) {
-        cerr << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
+        SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
         goto done;
     }
 
@@ -305,7 +344,7 @@
     }
 
     if (!descriptor) {
-        cerr << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
+        SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
         goto done;
     }
 
@@ -323,7 +362,7 @@
 done:
     if (!rv) {
         if (DLCLOSE(libraryHandle) != 0) {
-            cerr << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
+            SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
         }
     }
 
--- a/plugin/PiperVampPluginFactory.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/PiperVampPluginFactory.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -13,6 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
+#ifdef HAVE_PIPER
+
 #include "PiperVampPluginFactory.h"
 #include "PluginIdentifier.h"
 
@@ -50,27 +52,23 @@
 {
     QString serverName = "piper-vamp-simple-server";
 
-    m_servers = HelperExecPath::getHelperExecutables(serverName);
+    HelperExecPath hep(HelperExecPath::AllInstalled);
+    m_servers = hep.getHelperExecutables(serverName);
 
+    for (auto n: m_servers) {
+        SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: "
+                << n.executable << endl;
+    }
+    
     if (m_servers.empty()) {
-        cerr << "NOTE: No Piper Vamp servers found in installation;"
-             << " found none of the following:" << endl;
-        for (auto d: HelperExecPath::getHelperCandidatePaths(serverName)) {
-            cerr << "NOTE: " << d << endl;
+        SVDEBUG << "NOTE: No Piper Vamp servers found in installation;"
+                << " found none of the following:" << endl;
+        for (auto d: hep.getHelperCandidatePaths(serverName)) {
+            SVDEBUG << "NOTE: " << d << endl;
         }
     }
 }
 
-QStringList
-PiperVampPluginFactory::getServerSuffixes()
-{
-    if (sizeof(void *) == 8) {
-        return { "-64", "", "-32" };
-    } else {
-        return { "", "-32" };
-    }
-}
-
 vector<QString>
 PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
 {
@@ -104,6 +102,7 @@
 
     if (m_origins.find(identifier) == m_origins.end()) {
         cerr << "ERROR: No known server for identifier " << identifier << endl;
+        SVDEBUG << "ERROR: No known server for identifier " << identifier << endl;
         return 0;
     }
     
@@ -149,7 +148,7 @@
 {
     QString someError;
 
-    for (QString s: m_servers) {
+    for (auto s: m_servers) {
 
         populateFrom(s, someError);
 
@@ -160,20 +159,57 @@
 }
 
 void
-PiperVampPluginFactory::populateFrom(QString server, QString &errorMessage)
+PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server,
+                                     QString &errorMessage)
 {
-    piper_vamp::client::ProcessQtTransport transport(server.toStdString(),
-                                                     "capnp");
+    QString tag = server.tag;
+    string executable = server.executable.toStdString();
+
+    PluginScan *scan = PluginScan::getInstance();
+    auto candidateLibraries =
+        scan->getCandidateLibrariesFor(PluginScan::VampPlugin);
+
+    SVDEBUG << "INFO: Have " << candidateLibraries.size()
+            << " candidate Vamp plugin libraries" << endl;
+        
+    vector<string> from;
+    for (const auto &c: candidateLibraries) {
+        if (c.helperTag == tag) {
+            string soname = QFileInfo(c.libraryPath).baseName().toStdString();
+            SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
+            from.push_back(soname);
+        }
+    }
+
+    if (from.empty()) {
+        SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \""
+             << tag << "\"";
+        if (scan->scanSucceeded()) {
+            // we have to assume that they all failed to load (i.e. we
+            // exclude them all) rather than sending an empty list
+            // (which would mean no exclusions)
+            SVDEBUG << ", skipping" << endl;
+            return;
+        } else {
+            SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl;
+        }
+    }
+    
+    piper_vamp::client::ProcessQtTransport transport(executable, "capnp");
     if (!transport.isOK()) {
         errorMessage = QObject::tr("Could not start external plugin host");
         return;
     }
 
     piper_vamp::client::CapnpRRClient client(&transport);
-    piper_vamp::ListResponse lr;
+
+    piper_vamp::ListRequest req;
+    req.from = from;
+    
+    piper_vamp::ListResponse resp;
 
     try {
-        lr = client.listPluginData();
+        resp = client.listPluginData(req);
     } catch (piper_vamp::client::ServerCrashed) {
         errorMessage = QObject::tr
             ("External plugin host exited unexpectedly while listing plugins");
@@ -184,7 +220,10 @@
         return;
     }
 
-    for (const auto &pd: lr.available) {
+    SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists "
+            << resp.available.size() << " plugin(s)" << endl;
+
+    for (const auto &pd: resp.available) {
         
         QString identifier =
             QString("vamp:") + QString::fromStdString(pd.pluginKey);
@@ -195,7 +234,7 @@
             continue;
         }
 
-        m_origins[identifier] = server;
+        m_origins[identifier] = server.executable;
         
         m_pluginData[identifier] = pd;
 
@@ -208,3 +247,4 @@
     }
 }
 
+#endif
--- a/plugin/PiperVampPluginFactory.h	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/PiperVampPluginFactory.h	Fri Nov 04 14:57:03 2016 +0000
@@ -16,6 +16,8 @@
 #ifndef SV_PIPER_VAMP_PLUGIN_FACTORY_H
 #define SV_PIPER_VAMP_PLUGIN_FACTORY_H
 
+#ifdef HAVE_PIPER
+
 #include "FeatureExtractionPluginFactory.h"
 
 #include <QMutex>
@@ -23,6 +25,7 @@
 #include <map>
 
 #include "base/Debug.h"
+#include "base/HelperExecPath.h"
 
 /**
  * FeatureExtractionPluginFactory type for Vamp plugins hosted in a
@@ -49,15 +52,15 @@
 
 protected:
     QMutex m_mutex;
-    QStringList m_servers; // executable file paths
+    QList<HelperExecPath::HelperExec> m_servers; // executable file paths
     std::map<QString, QString> m_origins; // plugin identifier -> server path
     std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data
     std::map<QString, QString> m_taxonomy; // identifier -> category string
 
     void populate(QString &errorMessage);
-    void populateFrom(QString server, QString &errorMessage);
-
-    static QStringList getServerSuffixes();
+    void populateFrom(const HelperExecPath::HelperExec &, QString &errorMessage);
 };
 
 #endif
+
+#endif
--- a/plugin/PluginScan.cpp	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/PluginScan.cpp	Fri Nov 04 14:57:03 2016 +0000
@@ -15,24 +15,27 @@
 #include "PluginScan.h"
 
 #include "base/Debug.h"
+#include "base/Preferences.h"
 #include "base/HelperExecPath.h"
 
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
 #include "checker/knownplugins.h"
+#else
+class KnownPlugins {};
+#endif
 
 #include <QMutex>
 #include <QCoreApplication>
 
 using std::string;
 
-//#define DEBUG_PLUGIN_SCAN 1
-
-class PluginScan::Logger : public PluginCandidates::LogCallback
+class PluginScan::Logger
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+    : public PluginCandidates::LogCallback
+#endif
 {
 protected:
     void log(std::string message) {
-#ifdef DEBUG_PLUGIN_SCAN
-        cerr << "PluginScan: " << message;
-#endif
         SVDEBUG << "PluginScan: " << message;
     }
 };
@@ -47,10 +50,11 @@
     return m_instance;
 }
 
-PluginScan::PluginScan() : m_kp(0), m_succeeded(false), m_logger(new Logger) {
+PluginScan::PluginScan() : m_succeeded(false), m_logger(new Logger) {
 }
 
 PluginScan::~PluginScan() {
+    QMutexLocker locker(&m_mutex);
     clear();
     delete m_logger;
 }
@@ -58,34 +62,77 @@
 void
 PluginScan::scan()
 {
-    QStringList helperPaths =
-        HelperExecPath::getHelperExecutables("plugin-checker-helper");
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+
+    QMutexLocker locker(&m_mutex);
+
+    bool inProcess = Preferences::getInstance()->getRunPluginsInProcess();
+
+    HelperExecPath hep(inProcess ?
+                       HelperExecPath::NativeArchitectureOnly :
+                       HelperExecPath::AllInstalled);
+
+    QString helperName("plugin-checker-helper");
+    auto helpers = hep.getHelperExecutables(helperName);
 
     clear();
 
-    for (auto p: helperPaths) {
+    for (auto p: helpers) {
+        SVDEBUG << "NOTE: PluginScan: Found helper: " << p.executable << endl;
+    }
+    
+    if (helpers.empty()) {
+        SVDEBUG << "NOTE: No plugin checker helpers found in installation;"
+             << " found none of the following:" << endl;
+        for (auto d: hep.getHelperCandidatePaths(helperName)) {
+            SVDEBUG << "NOTE: " << d << endl;
+        }
+    }
+
+    for (auto p: helpers) {
         try {
-            KnownPlugins *kp = new KnownPlugins(p.toStdString(), m_logger);
-            m_kp.push_back(kp);
+            KnownPlugins *kp = new KnownPlugins
+                (p.executable.toStdString(), m_logger);
+            if (m_kp.find(p.tag) != m_kp.end()) {
+                SVDEBUG << "WARNING: PluginScan::scan: Duplicate tag " << p.tag
+                     << " for helpers" << endl;
+                continue;
+            }
+            m_kp[p.tag] = kp;
             m_succeeded = true;
         } catch (const std::exception &e) {
-            cerr << "ERROR: PluginScan::scan: " << e.what()
-                 << " (with helper path = " << p << ")" << endl;
+            SVDEBUG << "ERROR: PluginScan::scan: " << e.what()
+                 << " (with helper path = " << p.executable << ")" << endl;
         }
     }
+
+#endif
+}
+
+bool
+PluginScan::scanSucceeded() const
+{
+    QMutexLocker locker(&m_mutex);
+    return m_succeeded;
 }
 
 void
 PluginScan::clear()
 {
-    for (auto &p: m_kp) delete p;
+    for (auto &p: m_kp) {
+        delete p.second;
+    }
     m_kp.clear();
     m_succeeded = false;
 }
 
-QStringList
+QList<PluginScan::Candidate>
 PluginScan::getCandidateLibrariesFor(PluginType type) const
 {
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+    
+    QMutexLocker locker(&m_mutex);
+
     KnownPlugins::PluginType kpt;
     switch (type) {
     case VampPlugin: kpt = KnownPlugins::VampPlugin; break;
@@ -94,17 +141,48 @@
     default: throw std::logic_error("Inconsistency in plugin type enums");
     }
     
-    QStringList candidates;
-    for (auto kp: m_kp) {
+    QList<Candidate> candidates;
+
+    for (auto rec: m_kp) {
+
+        KnownPlugins *kp = rec.second;
+        
         auto c = kp->getCandidateLibrariesFor(kpt);
-        for (auto s: c) candidates.push_back(s.c_str());
+
+        SVDEBUG << "PluginScan: helper \"" << kp->getHelperExecutableName()
+                << "\" likes " << c.size() << " libraries of type "
+                << kp->getTagFor(kpt) << endl;
+
+        for (auto s: c) {
+            candidates.push_back({ s.c_str(), rec.first });
+        }
+
+        if (type != VampPlugin) {
+            // We are only interested in querying multiple helpers
+            // when dealing with Vamp plugins, for which we can use
+            // external servers and so in some cases can support
+            // additional architectures. Other plugin formats are
+            // loaded directly and so must match the host, which is
+            // what the first helper is supposed to handle -- so
+            // break after the first one if not querying Vamp
+            break;
+        }
     }
+
     return candidates;
+    
+#else
+    return {};
+#endif
 }
 
 QString
 PluginScan::getStartupFailureReport() const
 {
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+    
+    QMutexLocker locker(&m_mutex);
+
     if (!m_succeeded) {
 	return QObject::tr("<b>Failed to scan for plugins</b>"
 			   "<p>Failed to scan for plugins at startup. Possibly "
@@ -120,7 +198,7 @@
 
     QString report;
     for (auto kp: m_kp) {
-        report += QString::fromStdString(kp->getFailureReport());
+        report += QString::fromStdString(kp.second->getFailureReport());
     }
     if (report == "") {
 	return report;
@@ -132,5 +210,9 @@
         + QObject::tr("<p>These plugins may be incompatible with the system, "
                       "and will be ignored during this run of %1.</p>")
         .arg(QCoreApplication::applicationName());
+
+#else
+    return "";
+#endif
 }
 
--- a/plugin/PluginScan.h	Tue Nov 01 14:06:47 2016 +0000
+++ b/plugin/PluginScan.h	Fri Nov 04 14:57:03 2016 +0000
@@ -16,7 +16,9 @@
 #define PLUGIN_SCAN_H
 
 #include <QStringList>
+#include <QMutex>
 #include <vector>
+#include <map>
 
 class KnownPlugins;
 
@@ -25,8 +27,21 @@
 public:
     static PluginScan *getInstance();
 
+    /**
+     * Carry out startup scan of available plugins. Do not call
+     * getCandidateLibrariesFor() unless this has been called and
+     * scanSucceeded() is returning true.
+     */
     void scan();
 
+    /**
+     * Return true if scan() completed successfully. If the scan
+     * failed, consider using the normal plugin path to load any
+     * available plugins (as if they had all been found to be
+     * loadable) rather than rejecting all of them -- i.e. consider
+     * falling back on the behaviour of code from before the scan
+     * logic was added.
+     */
     bool scanSucceeded() const;
     
     enum PluginType {
@@ -34,7 +49,21 @@
 	LADSPAPlugin,
 	DSSIPlugin
     };
-    QStringList getCandidateLibrariesFor(PluginType) const;
+    struct Candidate {
+        QString libraryPath;    // full path, not just soname
+        QString helperTag;      // identifies the helper that found it
+                                // (see HelperExecPath) 
+    };
+
+    /**
+     * Return the candidate plugin libraries of the given type that
+     * were found by helpers during the startup scan.
+     *
+     * This could return an empty list for two reasons: the scan
+     * succeeded but no libraries were found; or the scan failed. Call
+     * scanSucceeded() to distinguish between them.
+     */
+    QList<Candidate> getCandidateLibrariesFor(PluginType) const;
 
     QString getStartupFailureReport() const;
 
@@ -43,8 +72,10 @@
     ~PluginScan();
 
     void clear();
+
+    mutable QMutex m_mutex; // while scanning; definitely can't multi-thread this
     
-    std::vector<KnownPlugins *> m_kp;
+    std::map<QString, KnownPlugins *> m_kp; // tag -> KnownPlugins client
     bool m_succeeded;
 
     class Logger;
--- a/svcore.pro	Tue Nov 01 14:06:47 2016 +0000
+++ b/svcore.pro	Fri Nov 04 14:57:03 2016 +0000
@@ -29,6 +29,6 @@
 
 include(files.pri)
 
-HEADERS = $$(SVCORE_HEADERS)
-SOURCES = $$(SVCORE_SOURCES)
+HEADERS = $$SVCORE_HEADERS
+SOURCES = $$SVCORE_SOURCES