# HG changeset patch # User Chris Cannam # Date 1478271423 0 # Node ID 67aee57e32c87bb5cb5d3cb178812acc0f5bbc57 # Parent c6bdf247016a9f264a05082f9725b93171d17dac# Parent c2d66e3c83d03e2a7f90fd72e263d1ccbd3b5395 Merge from branch piper diff -r c6bdf247016a -r 67aee57e32c8 base/Debug.cpp --- 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 #include -#ifndef NDEBUG +#include 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) { diff -r c6bdf247016a -r 67aee57e32c8 base/Debug.h --- 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 - inline NoDebug &operator<<(const T &) { return *this; } - - inline NoDebug &operator<<(QTextStreamFunction) { return *this; } -}; - -#define SVDEBUG NoDebug() - -#endif /* !NDEBUG */ - #endif /* !_DEBUG_H_ */ diff -r c6bdf247016a -r 67aee57e32c8 base/HelperExecPath.cpp --- /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 +#include +#include +#include + +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::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::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 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; +} + diff -r c6bdf247016a -r 67aee57e32c8 base/HelperExecPath.h --- /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 + +/** + * 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 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 search(QString, QStringList &); + QStringList getTags(); +}; + +#endif diff -r c6bdf247016a -r 67aee57e32c8 base/ResourceFinder.cpp --- 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 #include +#include /** 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 diff -r c6bdf247016a -r 67aee57e32c8 plugin/FeatureExtractionPluginFactory.cpp --- 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 #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; diff -r c6bdf247016a -r 67aee57e32c8 plugin/LADSPAPluginFactory.cpp --- 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); } } diff -r c6bdf247016a -r 67aee57e32c8 plugin/NativeVampPluginFactory.cpp --- 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 +getCandidateLibraries() +{ +#ifdef HAVE_PLUGIN_CHECKER_HELPER + return PluginScan::getInstance()->getCandidateLibrariesFor + (PluginScan::VampPlugin); +#else + auto path = Vamp::PluginHostAdapter::getPluginPath(); + QList 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 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; } } diff -r c6bdf247016a -r 67aee57e32c8 plugin/PiperVampPluginFactory.cpp --- 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 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 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 diff -r c6bdf247016a -r 67aee57e32c8 plugin/PiperVampPluginFactory.h --- 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 @@ -23,6 +25,7 @@ #include #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 m_servers; // executable file paths std::map m_origins; // plugin identifier -> server path std::map m_pluginData; // identifier -> data std::map 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 diff -r c6bdf247016a -r 67aee57e32c8 plugin/PluginScan.cpp --- 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 #include 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::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 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("Failed to scan for plugins" "

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("

These plugins may be incompatible with the system, " "and will be ignored during this run of %1.

") .arg(QCoreApplication::applicationName()); + +#else + return ""; +#endif } diff -r c6bdf247016a -r 67aee57e32c8 plugin/PluginScan.h --- 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 +#include #include +#include 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 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 m_kp; + std::map m_kp; // tag -> KnownPlugins client bool m_succeeded; class Logger; diff -r c6bdf247016a -r 67aee57e32c8 svcore.pro --- 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