changeset 8:25e00373f597

Much renaming to ease inclusion into another project
author Chris Cannam
date Thu, 14 Apr 2016 16:52:19 +0100
parents 846464771d06
children af46a17798be
files .hgignore checker-client.pro checker.cpp checker.pri checker.pro checker/knownplugins.h checker/plugincandidates.h helper.cpp helper.pro knownplugins.cpp knownplugins.h plugincandidates.cpp plugincandidates.h src/checker.cpp src/helper.cpp src/knownplugins.cpp src/plugincandidates.cpp vamp-plugin-load-checker.pro
diffstat 18 files changed, 840 insertions(+), 837 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Apr 14 09:42:26 2016 +0100
+++ b/.hgignore	Thu Apr 14 16:52:19 2016 +0100
@@ -2,6 +2,6 @@
 *~
 *.o
 *.a
-checker
-helper
 Makefile*
+checker-client
+plugin-checker-helper
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/checker-client.pro	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,14 @@
+
+TEMPLATE = app
+
+include(checker.pri)
+
+# Using the "console" CONFIG flag above should ensure this happens for
+# normal Windows builds, but this may be necessary when cross-compiling
+win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console
+    
+TARGET = checker-client
+
+SOURCES += \
+	src/checker.cpp
+
--- a/checker.cpp	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#include "knownplugins.h"
-
-#include <iostream>
-
-using namespace std;
-
-struct LogCallback : PluginCandidates::LogCallback {
-    virtual void log(string message) {
-        cerr << "checker: log: " << message;
-    }
-};
-
-int main(int, char **)
-{
-    LogCallback cb;
-    KnownPlugins kp("./helper", &cb); //!!!
-    
-    for (auto t: kp.getKnownPluginTypes()) {
-	cout << "successful libraries for plugin type \""
-	     << kp.getTagFor(t) << "\":" << endl;
-	for (auto lib: kp.getCandidateLibrariesFor(t)) {
-	    cout << lib << endl;
-	}
-    }
-
-    cout << "Failure message (if any):" << endl;
-    cout << kp.getFailureReport() << endl;
-}
-
--- a/checker.pri	Thu Apr 14 09:42:26 2016 +0100
+++ b/checker.pri	Thu Apr 14 16:52:19 2016 +0100
@@ -9,12 +9,14 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
+INCLUDEPATH += checker
+
 HEADERS += \
-	plugincandidates.h \
-	knownplugins.h
+	checker/plugincandidates.h \
+	checker/knownplugins.h
 
 SOURCES += \
-	plugincandidates.cpp \
-	knownplugins.cpp
+	src/plugincandidates.cpp \
+	src/knownplugins.cpp
 
         
--- a/checker.pro	Thu Apr 14 09:42:26 2016 +0100
+++ b/checker.pro	Thu Apr 14 16:52:19 2016 +0100
@@ -1,14 +1,8 @@
 
-TEMPLATE = app
+TEMPLATE = subdirs
+SUBDIRS = sub_checker_lib sub_checker_client sub_helper
 
-include(checker.pri)
+sub_checker_lib.file = checker-lib.pro
+sub_checker_client.file = checker-client.pro
+sub_helper.file = helper.pro
 
-# Using the "console" CONFIG flag above should ensure this happens for
-# normal Windows builds, but this may be necessary when cross-compiling
-win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console
-    
-TARGET = checker
-
-SOURCES += \
-	checker.cpp
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/checker/knownplugins.h	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,81 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#ifndef KNOWN_PLUGINS_H
+#define KNOWN_PLUGINS_H
+
+#include "plugincandidates.h"
+
+#include <string>
+#include <map>
+#include <vector>
+
+class KnownPlugins
+{
+    typedef std::vector<std::string> stringlist;
+    
+public:
+    enum PluginType {
+	VampPlugin,
+	LADSPAPlugin,
+	DSSIPlugin
+    };
+
+    KnownPlugins(std::string helperExecutableName,
+                 PluginCandidates::LogCallback *cb = 0);
+
+    std::vector<PluginType> getKnownPluginTypes() const {
+	return { VampPlugin, LADSPAPlugin, DSSIPlugin };
+    };
+    
+    std::string getTagFor(PluginType type) const {
+	return m_known.at(type).tag;
+    }
+
+    stringlist getCandidateLibrariesFor(PluginType type) const {
+	return m_candidates.getCandidateLibrariesFor(getTagFor(type));
+    }
+
+    std::string getFailureReport() const;
+    
+private:
+    struct TypeRec {
+	std::string tag;
+	stringlist path;
+	std::string descriptor;
+    };
+    std::map<PluginType, TypeRec> m_known;
+
+    stringlist expandConventionalPath(PluginType type, std::string var);
+    std::string getDefaultPath(PluginType type);
+
+    PluginCandidates m_candidates;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/checker/plugincandidates.h	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,105 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#ifndef PLUGIN_CANDIDATES_H
+#define PLUGIN_CANDIDATES_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+/**
+ * Class to identify and list candidate shared-library files possibly
+ * containing plugins. Uses a separate process (the "helper", whose
+ * executable name must be provided at construction) to test-load each
+ * library in order to winnow out any that fail to load or crash on
+ * load.
+ *
+ * Requires C++11 and the Qt5 QtCore library.
+ */
+class PluginCandidates
+{
+    typedef std::vector<std::string> stringlist;
+
+public:
+    /** Construct a PluginCandidates scanner that uses the given
+     *  executable as its load check helper.
+     */
+    PluginCandidates(std::string helperExecutableName);
+
+    struct LogCallback {
+        virtual ~LogCallback() { }
+        virtual void log(std::string) = 0;
+    };
+    
+    /** Set a callback to be called for log output.
+     */
+    void setLogCallback(LogCallback *cb);
+
+    /** Scan the libraries found in the given plugin path (i.e. list
+     *  of plugin directories), checking that the given descriptor
+     *  symbol can be looked up in each. Store the results
+     *  internally, associated with the given (arbitrary) tag, for
+     *  later querying using getCandidateLibrariesFor() and
+     *  getFailedLibrariesFor().
+     *
+     *  Not thread-safe.
+     */
+    void scan(std::string tag,
+	      stringlist pluginPath,
+	      std::string descriptorSymbolName);
+
+    /** Return list of plugin library paths that were checked
+     *  successfully during the scan for the given tag.
+     */
+    stringlist getCandidateLibrariesFor(std::string tag) const;
+
+    struct FailureRec {
+	std::string library;
+	std::string message;
+    };
+
+    /** Return list of failure reports arising from the prior scan for
+     *  the given tag. 
+     */
+    std::vector<FailureRec> getFailedLibrariesFor(std::string tag) const;
+
+private:
+    std::string m_helper;
+    std::map<std::string, stringlist> m_candidates;
+    std::map<std::string, std::vector<FailureRec> > m_failures;
+    LogCallback *m_logCallback;
+
+    stringlist getLibrariesInPath(stringlist path);
+    stringlist runHelper(stringlist libraries, std::string descriptor);
+    void recordResult(std::string tag, stringlist results);
+    void log(std::string);
+};
+
+#endif
--- a/helper.cpp	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/**
- * Plugin Load Checker Helper
- *
- * This program accepts the name of a descriptor symbol as its only
- * command-line argument. It then reads a list of plugin library paths
- * from stdin, one per line. For each path read, it attempts to load
- * that library and retrieve the named descriptor symbol, printing a
- * line to stdout reporting whether this was successful or not and
- * then flushing stdout. The output line format is described
- * below. The program exits with code 0 if all libraries were loaded
- * successfully and non-zero otherwise.
- *
- * Note that library paths must be ready to pass to dlopen() or
- * equivalent; this usually means they should be absolute paths.
- *
- * Output line for successful load of library libname.so:
- * SUCCESS|/path/to/libname.so|
- * 
- * Output line for failed load of library libname.so:
- * FAILURE|/path/to/libname.so|Reason for failure if available
- *
- * Sometimes plugins will crash completely on load, bringing down this
- * program with them. If the program exits before all listed plugins
- * have been checked, this means that the plugin following the last
- * reported one has crashed. Typically the caller may want to run it
- * again, omitting that plugin.
- */
-
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#ifdef _WIN32
-#include <windows.h>
-#include <process.h>
-#define DLOPEN(a,b)  LoadLibrary((a).toStdWString().c_str())
-#define DLSYM(a,b)   GetProcAddress((HINSTANCE)(a),(b))
-#define DLCLOSE(a)   (!FreeLibrary((HINSTANCE)(a)))
-#define DLERROR()    ""
-#else
-#include <dlfcn.h>
-#define DLOPEN(a,b)  dlopen((a).c_str(),(b))
-#define DLSYM(a,b)   dlsym((a),(b).c_str())
-#define DLCLOSE(a)   dlclose((a))
-#define DLERROR()    dlerror()
-#endif
-
-#include <string>
-#include <iostream>
-
-#include <unistd.h>
-
-using namespace std;
-
-string error()
-{
-    string e = dlerror();
-    if (e == "") return "(unknown error)";
-    else return e;
-}
-
-string check(string soname, string descriptor)
-{
-    void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
-    if (!handle) {
-	return "Unable to open plugin library: " + error();
-    }
-
-    void *fn = DLSYM(handle, descriptor);
-    if (!fn) {
-	return "Failed to find plugin descriptor " + descriptor +
-	    " in library: " + error();
-    }
-
-    return "";
-}
-
-int main(int argc, char **argv)
-{
-    bool allGood = true;
-    string soname;
-
-    if (argc != 2) {
-	cerr << "\nUsage:\n    " << argv[0] << " descriptorname\n"
-	    "\nwhere descriptorname is the name of a plugin descriptor symbol to be sought\n"
-	    "in each library (e.g. vampGetPluginDescriptor for Vamp plugins). The list of\n"
-	    "candidate plugin library filenames is read from stdin.\n" << endl;
-	return 2;
-    }
-
-    string descriptor = argv[1];
-    
-    while (getline(cin, soname)) {
-	string report = check(soname, descriptor);
-	if (report != "") {
-	    cout << "FAILURE|" << soname << "|" << report << endl;
-	    allGood = false;
-	} else {
-	    cout << "SUCCESS|" << soname << "|" << endl;
-	}
-    }
-
-    return allGood ? 0 : 1;
-}
--- a/helper.pro	Thu Apr 14 09:42:26 2016 +0100
+++ b/helper.pro	Thu Apr 14 16:52:19 2016 +0100
@@ -16,11 +16,11 @@
     QMAKE_LFLAGS += -ldl
 }
 
-TARGET = helper
+TARGET = plugin-checker-helper
 
 OBJECTS_DIR = o
 MOC_DIR = o
 
 SOURCES += \
-	helper.cpp
+	src/helper.cpp
 
--- a/knownplugins.cpp	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#include "knownplugins.h"
-
-#include <sstream>
-
-using namespace std;
-
-#if defined(_WIN32)
-#define PATH_SEPARATOR ';'
-#else
-#define PATH_SEPARATOR ':'
-#endif
-
-KnownPlugins::KnownPlugins(string helperExecutableName,
-                           PluginCandidates::LogCallback *cb) :
-    m_candidates(helperExecutableName)
-{
-    m_candidates.setLogCallback(cb);
-    
-    m_known = {
-	{
-	    VampPlugin,
-	    {
-		"vamp",
-		expandConventionalPath(VampPlugin, "VAMP_PATH"),
-		"vampGetPluginDescriptor"
-	    },
-	}, {
-	    LADSPAPlugin,
-	    {
-		"ladspa",
-		expandConventionalPath(LADSPAPlugin, "LADSPA_PATH"),
-		"ladspa_descriptor"
-	    },
-	}, {
-	    DSSIPlugin,
-	    {
-		"dssi",
-		expandConventionalPath(DSSIPlugin, "DSSI_PATH"),
-		"dssi_descriptor"
-	    }
-	}
-    };
-
-    for (const auto &k: m_known) {
-	m_candidates.scan(k.second.tag, k.second.path, k.second.descriptor);
-    }
-}
-
-string
-KnownPlugins::getDefaultPath(PluginType type)
-{
-    switch (type) {
-
-#if defined(_WIN32)
-
-    case VampPlugin:
-	return "%ProgramFiles%\\Vamp Plugins";
-    case LADSPAPlugin:
-	return "%ProgramFiles%\\LADSPA Plugins;%ProgramFiles%\\Audacity\\Plug-Ins";
-    case DSSIPlugin:
-	return "%ProgramFiles%\\DSSI Plugins";
-	
-#elif defined(__APPLE__)
-	
-    case VampPlugin:
-	return "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp";
-    case LADSPAPlugin:
-	return "$HOME/Library/Audio/Plug-Ins/LADSPA:/Library/Audio/Plug-Ins/LADSPA";
-    case DSSIPlugin:
-	return "$HOME/Library/Audio/Plug-Ins/DSSI:/Library/Audio/Plug-Ins/DSSI";
-	
-#else /* Linux, BSDs, etc */
-	
-    case VampPlugin:
-	return "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp";
-    case LADSPAPlugin:
-	return "$HOME/ladspa:$HOME/.ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa";
-    case DSSIPlugin:
-	return "$HOME/dssi:$HOME/.dssi:/usr/local/lib/dssi:/usr/lib/dssi";
-#endif
-    }
-
-    throw logic_error("unknown or unhandled plugin type");
-}
-
-vector<string>
-KnownPlugins::expandConventionalPath(PluginType type, string var)
-{
-    vector<string> pathList;
-    string path;
-
-    char *cpath = getenv(var.c_str());
-    if (cpath) path = cpath;
-
-    if (path == "") {
-
-        path = getDefaultPath(type);
-
-	if (path != "") {
-
-	    char *home = getenv("HOME");
-	    if (home) {
-		string::size_type f;
-		while ((f = path.find("$HOME")) != string::npos &&
-		       f < path.length()) {
-		    path.replace(f, 5, home);
-		}
-	    }
-
-#ifdef _WIN32
-	    char *pfiles = getenv("ProgramFiles");
-	    if (!pfiles) pfiles = "C:\\Program Files";
-	    {
-		string::size_type f;
-		while ((f = path.find("%ProgramFiles%")) != string::npos &&
-		       f < path.length()) {
-		    path.replace(f, 14, pfiles);
-		}
-	    }
-#endif
-	}
-    }
-
-    string::size_type index = 0, newindex = 0;
-
-    while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) {
-	pathList.push_back(path.substr(index, newindex - index).c_str());
-	index = newindex + 1;
-    }
-    
-    pathList.push_back(path.substr(index));
-
-    return pathList;
-}
-
-string
-KnownPlugins::getFailureReport() const
-{
-    vector<PluginCandidates::FailureRec> failures;
-
-    for (auto t: getKnownPluginTypes()) {
-	auto ff = m_candidates.getFailedLibrariesFor(getTagFor(t));
-	failures.insert(failures.end(), ff.begin(), ff.end());
-    }
-
-    if (failures.empty()) return "";
-
-    int n = failures.size();
-    int i = 0;
-
-    ostringstream os;
-    
-    os << "<ul>";
-    for (auto f: failures) {
-	os << "<li>" + f.library;
-	if (f.message != "") {
-	    os << " (" + f.message + ")";
-	} else {
-	    os << " (unknown error)";
-	}
-	os << "</li>";
-
-	if (n > 10) {
-	    if (++i == 5) {
-		os << "<li>(... and " << (n - i) << " further failures)</li>";
-		break;
-	    }
-	}
-    }
-    os << "</ul>";
-
-    return os.str();
-}
--- a/knownplugins.h	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#ifndef KNOWN_PLUGINS_H
-#define KNOWN_PLUGINS_H
-
-#include "plugincandidates.h"
-
-#include <string>
-#include <map>
-#include <vector>
-
-class KnownPlugins
-{
-    typedef std::vector<std::string> stringlist;
-    
-public:
-    enum PluginType {
-	VampPlugin,
-	LADSPAPlugin,
-	DSSIPlugin
-    };
-
-    KnownPlugins(std::string helperExecutableName,
-                 PluginCandidates::LogCallback *cb = 0);
-
-    std::vector<PluginType> getKnownPluginTypes() const {
-	return { VampPlugin, LADSPAPlugin, DSSIPlugin };
-    };
-    
-    std::string getTagFor(PluginType type) const {
-	return m_known.at(type).tag;
-    }
-
-    stringlist getCandidateLibrariesFor(PluginType type) const {
-	return m_candidates.getCandidateLibrariesFor(getTagFor(type));
-    }
-
-    std::string getFailureReport() const;
-    
-private:
-    struct TypeRec {
-	std::string tag;
-	stringlist path;
-	std::string descriptor;
-    };
-    std::map<PluginType, TypeRec> m_known;
-
-    stringlist expandConventionalPath(PluginType type, std::string var);
-    std::string getDefaultPath(PluginType type);
-
-    PluginCandidates m_candidates;
-};
-
-#endif
--- a/plugincandidates.cpp	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,235 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#include "plugincandidates.h"
-
-#include <set>
-#include <stdexcept>
-#include <iostream>
-
-#include <QProcess>
-#include <QDir>
-#include <QTime>
-
-#if defined(_WIN32)
-#define PLUGIN_GLOB "*.dll"
-#elif defined(__APPLE__)
-#define PLUGIN_GLOB "*.dylib *.so"
-#else
-#define PLUGIN_GLOB "*.so"
-#endif
-
-using namespace std;
-
-PluginCandidates::PluginCandidates(string helperExecutableName) :
-    m_helper(helperExecutableName),
-    m_logCallback(0)
-{
-}
-
-void
-PluginCandidates::setLogCallback(LogCallback *cb)
-{
-    m_logCallback = cb;
-}
-
-vector<string>
-PluginCandidates::getCandidateLibrariesFor(string tag) const
-{
-    if (m_candidates.find(tag) == m_candidates.end()) return {};
-    else return m_candidates.at(tag);
-}
-
-vector<PluginCandidates::FailureRec>
-PluginCandidates::getFailedLibrariesFor(string tag) const
-{
-    if (m_failures.find(tag) == m_failures.end()) return {};
-    else return m_failures.at(tag);
-}
-
-void
-PluginCandidates::log(string message)
-{
-    if (m_logCallback) m_logCallback->log("PluginCandidates: " + message);
-}
-
-vector<string>
-PluginCandidates::getLibrariesInPath(vector<string> path)
-{
-    vector<string> candidates;
-
-    for (string dirname: path) {
-
-        log("scanning directory " + dirname + "\n");
-
-        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.toStdString());
-        }
-    }
-
-    return candidates;
-}
-
-void
-PluginCandidates::scan(string tag,
-		       vector<string> pluginPath,
-		       string descriptorSymbolName)
-{
-    vector<string> libraries = getLibrariesInPath(pluginPath);
-    vector<string> remaining = libraries;
-
-    int runlimit = 20;
-    int runcount = 0;
-    
-    vector<string> result;
-    
-    while (result.size() < libraries.size() && runcount < runlimit) {
-	vector<string> output = runHelper(remaining, descriptorSymbolName);
-	result.insert(result.end(), output.begin(), output.end());
-	int shortfall = int(remaining.size()) - int(output.size());
-	if (shortfall > 0) {
-	    // Helper bailed out for some reason presumably associated
-	    // with the plugin following the last one it reported
-	    // on. Add a failure entry for that one and continue with
-	    // the following ones.
-            string failed = *(remaining.rbegin() + shortfall - 1);
-            log("helper output ended before result for plugin " + failed + "\n");
-	    result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out");
-            remaining = vector<string>
-                (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
-	}
-	++runcount;
-    }
-
-    recordResult(tag, result);
-}
-
-vector<string>
-PluginCandidates::runHelper(vector<string> libraries, string descriptor)
-{
-    vector<string> output;
-
-    log("running helper with following library list:\n");
-    for (auto &lib: libraries) log(lib + "\n");
-
-    QProcess process;
-    process.setReadChannel(QProcess::StandardOutput);
-    process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
-    process.start(m_helper.c_str(), { descriptor.c_str() });
-    if (!process.waitForStarted()) {
-	cerr << "helper failed to start" << endl;
-	throw runtime_error("plugin load helper failed to start");
-    }
-    for (auto &lib: libraries) {
-	process.write(lib.c_str(), lib.size());
-	process.write("\n", 1);
-    }
-
-    QTime t;
-    t.start();
-    int timeout = 3000; // ms
-
-    int buflen = 4096;
-    bool done = false;
-    
-    while (!done) {
-	char buf[buflen];
-	qint64 linelen = process.readLine(buf, buflen);
-        if (linelen > 0) {
-            output.push_back(buf);
-            done = (output.size() == libraries.size());
-        } else if (linelen < 0) {
-            // error case
-            log("received error code while reading from helper\n");
-            done = true;
-	} else {
-            // no error, but no line read (could just be between
-            // lines, or could be eof)
-            done = (process.state() == QProcess::NotRunning);
-            if (!done) {
-                if (t.elapsed() > timeout) {
-                    // this is purely an emergency measure
-                    log("timeout: helper took too long, killing it\n");
-                    process.kill();
-                    done = true;
-                } else {
-                    process.waitForReadyRead(200);
-                }
-            }
-        }
-    }
-
-    if (process.state() != QProcess::NotRunning) {
-        process.close();
-        process.waitForFinished();
-    }
-	
-    return output;
-}
-
-void
-PluginCandidates::recordResult(string tag, vector<string> result)
-{
-    for (auto &r: result) {
-
-        QString s(r.c_str());
-        QStringList bits = s.split("|");
-
-        log("read output line from helper: " + r);
-        
-        if (bits.size() < 2 || bits.size() > 3) {
-            log("invalid output line (wrong number of |-separated fields)\n");
-            continue;
-        }
-
-        string status = bits[0].toStdString();
-        
-        string library = bits[1].toStdString();
-        if (bits.size() == 2) library = bits[1].trimmed().toStdString();
-
-        string message = "";
-        if (bits.size() > 2) message = bits[2].trimmed().toStdString();
-        
-        if (status == "SUCCESS") {
-            m_candidates[tag].push_back(library);
-
-        } else if (status == "FAILURE") {
-            m_failures[tag].push_back({ library, message });
-
-        } else {
-            log("unexpected status \"" + status + "\" in output line\n");
-        }
-    }
-}
-
--- a/plugincandidates.h	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Copyright (c) 2016 Queen Mary, University of London
-
-    Permission is hereby granted, free of charge, to any person
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without
-    restriction, including without limitation the rights to use, copy,
-    modify, merge, publish, distribute, sublicense, and/or sell copies
-    of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-    Except as contained in this notice, the names of the Centre for
-    Digital Music and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#ifndef PLUGIN_CANDIDATES_H
-#define PLUGIN_CANDIDATES_H
-
-#include <string>
-#include <vector>
-#include <map>
-
-/**
- * Class to identify and list candidate shared-library files possibly
- * containing plugins. Uses a separate process (the "helper", whose
- * executable name must be provided at construction) to test-load each
- * library in order to winnow out any that fail to load or crash on
- * load.
- *
- * Requires C++11 and the Qt5 QtCore library.
- */
-class PluginCandidates
-{
-    typedef std::vector<std::string> stringlist;
-
-public:
-    /** Construct a PluginCandidates scanner that uses the given
-     *  executable as its load check helper.
-     */
-    PluginCandidates(std::string helperExecutableName);
-
-    struct LogCallback {
-        virtual void log(std::string) = 0;
-    };
-    
-    /** Set a callback to be called for log output.
-     */
-    void setLogCallback(LogCallback *cb);
-
-    /** Scan the libraries found in the given plugin path (i.e. list
-     *  of plugin directories), checking that the given descriptor
-     *  symbol can be looked up in each. Store the results
-     *  internally, associated with the given (arbitrary) tag, for
-     *  later querying using getCandidateLibrariesFor() and
-     *  getFailedLibrariesFor().
-     *
-     *  Not thread-safe.
-     */
-    void scan(std::string tag,
-	      stringlist pluginPath,
-	      std::string descriptorSymbolName);
-
-    /** Return list of plugin library paths that were checked
-     *  successfully during the scan for the given tag.
-     */
-    stringlist getCandidateLibrariesFor(std::string tag) const;
-
-    struct FailureRec {
-	std::string library;
-	std::string message;
-    };
-
-    /** Return list of failure reports arising from the prior scan for
-     *  the given tag. 
-     */
-    std::vector<FailureRec> getFailedLibrariesFor(std::string tag) const;
-
-private:
-    std::string m_helper;
-    std::map<std::string, stringlist> m_candidates;
-    std::map<std::string, std::vector<FailureRec> > m_failures;
-    LogCallback *m_logCallback;
-
-    stringlist getLibrariesInPath(stringlist path);
-    stringlist runHelper(stringlist libraries, std::string descriptor);
-    void recordResult(std::string tag, stringlist results);
-    void log(std::string);
-};
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/checker.cpp	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,58 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#include "knownplugins.h"
+
+#include <iostream>
+
+using namespace std;
+
+struct LogCallback : PluginCandidates::LogCallback {
+    virtual void log(string message) {
+        cerr << "checker: log: " << message;
+    }
+};
+
+int main(int, char **)
+{
+    LogCallback cb;
+    KnownPlugins kp("./plugin-checker-helper", &cb); //!!!
+    
+    for (auto t: kp.getKnownPluginTypes()) {
+	cout << "successful libraries for plugin type \""
+	     << kp.getTagFor(t) << "\":" << endl;
+	for (auto lib: kp.getCandidateLibrariesFor(t)) {
+	    cout << lib << endl;
+	}
+    }
+
+    cout << "Failure message (if any):" << endl;
+    cout << kp.getFailureReport() << endl;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/helper.cpp	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,130 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/**
+ * Plugin Load Checker Helper
+ *
+ * This program accepts the name of a descriptor symbol as its only
+ * command-line argument. It then reads a list of plugin library paths
+ * from stdin, one per line. For each path read, it attempts to load
+ * that library and retrieve the named descriptor symbol, printing a
+ * line to stdout reporting whether this was successful or not and
+ * then flushing stdout. The output line format is described
+ * below. The program exits with code 0 if all libraries were loaded
+ * successfully and non-zero otherwise.
+ *
+ * Note that library paths must be ready to pass to dlopen() or
+ * equivalent; this usually means they should be absolute paths.
+ *
+ * Output line for successful load of library libname.so:
+ * SUCCESS|/path/to/libname.so|
+ * 
+ * Output line for failed load of library libname.so:
+ * FAILURE|/path/to/libname.so|Reason for failure if available
+ *
+ * Sometimes plugins will crash completely on load, bringing down this
+ * program with them. If the program exits before all listed plugins
+ * have been checked, this means that the plugin following the last
+ * reported one has crashed. Typically the caller may want to run it
+ * again, omitting that plugin.
+ */
+
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#ifdef _WIN32
+#include <windows.h>
+#include <process.h>
+#define DLOPEN(a,b)  LoadLibrary((a).toStdWString().c_str())
+#define DLSYM(a,b)   GetProcAddress((HINSTANCE)(a),(b))
+#define DLCLOSE(a)   (!FreeLibrary((HINSTANCE)(a)))
+#define DLERROR()    ""
+#else
+#include <dlfcn.h>
+#define DLOPEN(a,b)  dlopen((a).c_str(),(b))
+#define DLSYM(a,b)   dlsym((a),(b).c_str())
+#define DLCLOSE(a)   dlclose((a))
+#define DLERROR()    dlerror()
+#endif
+
+#include <string>
+#include <iostream>
+
+#include <unistd.h>
+
+using namespace std;
+
+string error()
+{
+    string e = dlerror();
+    if (e == "") return "(unknown error)";
+    else return e;
+}
+
+string check(string soname, string descriptor)
+{
+    void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
+    if (!handle) {
+	return "Unable to open plugin library: " + error();
+    }
+
+    void *fn = DLSYM(handle, descriptor);
+    if (!fn) {
+	return "Failed to find plugin descriptor " + descriptor +
+	    " in library: " + error();
+    }
+
+    return "";
+}
+
+int main(int argc, char **argv)
+{
+    bool allGood = true;
+    string soname;
+
+    if (argc != 2) {
+	cerr << "\nUsage:\n    " << argv[0] << " descriptorname\n"
+	    "\nwhere descriptorname is the name of a plugin descriptor symbol to be sought\n"
+	    "in each library (e.g. vampGetPluginDescriptor for Vamp plugins). The list of\n"
+	    "candidate plugin library filenames is read from stdin.\n" << endl;
+	return 2;
+    }
+
+    string descriptor = argv[1];
+    
+    while (getline(cin, soname)) {
+	string report = check(soname, descriptor);
+	if (report != "") {
+	    cout << "FAILURE|" << soname << "|" << report << endl;
+	    allGood = false;
+	} else {
+	    cout << "SUCCESS|" << soname << "|" << endl;
+	}
+    }
+
+    return allGood ? 0 : 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/knownplugins.cpp	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,202 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#include "knownplugins.h"
+
+#include <sstream>
+
+using namespace std;
+
+#if defined(_WIN32)
+#define PATH_SEPARATOR ';'
+#else
+#define PATH_SEPARATOR ':'
+#endif
+
+KnownPlugins::KnownPlugins(string helperExecutableName,
+                           PluginCandidates::LogCallback *cb) :
+    m_candidates(helperExecutableName)
+{
+    m_candidates.setLogCallback(cb);
+    
+    m_known = {
+	{
+	    VampPlugin,
+	    {
+		"vamp",
+		expandConventionalPath(VampPlugin, "VAMP_PATH"),
+		"vampGetPluginDescriptor"
+	    },
+	}, {
+	    LADSPAPlugin,
+	    {
+		"ladspa",
+		expandConventionalPath(LADSPAPlugin, "LADSPA_PATH"),
+		"ladspa_descriptor"
+	    },
+	}, {
+	    DSSIPlugin,
+	    {
+		"dssi",
+		expandConventionalPath(DSSIPlugin, "DSSI_PATH"),
+		"dssi_descriptor"
+	    }
+	}
+    };
+
+    for (const auto &k: m_known) {
+	m_candidates.scan(k.second.tag, k.second.path, k.second.descriptor);
+    }
+}
+
+string
+KnownPlugins::getDefaultPath(PluginType type)
+{
+    switch (type) {
+
+#if defined(_WIN32)
+
+    case VampPlugin:
+	return "%ProgramFiles%\\Vamp Plugins";
+    case LADSPAPlugin:
+	return "%ProgramFiles%\\LADSPA Plugins;%ProgramFiles%\\Audacity\\Plug-Ins";
+    case DSSIPlugin:
+	return "%ProgramFiles%\\DSSI Plugins";
+	
+#elif defined(__APPLE__)
+	
+    case VampPlugin:
+	return "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp";
+    case LADSPAPlugin:
+	return "$HOME/Library/Audio/Plug-Ins/LADSPA:/Library/Audio/Plug-Ins/LADSPA";
+    case DSSIPlugin:
+	return "$HOME/Library/Audio/Plug-Ins/DSSI:/Library/Audio/Plug-Ins/DSSI";
+	
+#else /* Linux, BSDs, etc */
+	
+    case VampPlugin:
+	return "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp";
+    case LADSPAPlugin:
+	return "$HOME/ladspa:$HOME/.ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa";
+    case DSSIPlugin:
+	return "$HOME/dssi:$HOME/.dssi:/usr/local/lib/dssi:/usr/lib/dssi";
+#endif
+    }
+
+    throw logic_error("unknown or unhandled plugin type");
+}
+
+vector<string>
+KnownPlugins::expandConventionalPath(PluginType type, string var)
+{
+    vector<string> pathList;
+    string path;
+
+    char *cpath = getenv(var.c_str());
+    if (cpath) path = cpath;
+
+    if (path == "") {
+
+        path = getDefaultPath(type);
+
+	if (path != "") {
+
+	    char *home = getenv("HOME");
+	    if (home) {
+		string::size_type f;
+		while ((f = path.find("$HOME")) != string::npos &&
+		       f < path.length()) {
+		    path.replace(f, 5, home);
+		}
+	    }
+
+#ifdef _WIN32
+	    char *pfiles = getenv("ProgramFiles");
+	    if (!pfiles) pfiles = "C:\\Program Files";
+	    {
+		string::size_type f;
+		while ((f = path.find("%ProgramFiles%")) != string::npos &&
+		       f < path.length()) {
+		    path.replace(f, 14, pfiles);
+		}
+	    }
+#endif
+	}
+    }
+
+    string::size_type index = 0, newindex = 0;
+
+    while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) {
+	pathList.push_back(path.substr(index, newindex - index).c_str());
+	index = newindex + 1;
+    }
+    
+    pathList.push_back(path.substr(index));
+
+    return pathList;
+}
+
+string
+KnownPlugins::getFailureReport() const
+{
+    vector<PluginCandidates::FailureRec> failures;
+
+    for (auto t: getKnownPluginTypes()) {
+	auto ff = m_candidates.getFailedLibrariesFor(getTagFor(t));
+	failures.insert(failures.end(), ff.begin(), ff.end());
+    }
+
+    if (failures.empty()) return "";
+
+    int n = failures.size();
+    int i = 0;
+
+    ostringstream os;
+    
+    os << "<ul>";
+    for (auto f: failures) {
+	os << "<li>" + f.library;
+	if (f.message != "") {
+	    os << " (" + f.message + ")";
+	} else {
+	    os << " (unknown error)";
+	}
+	os << "</li>";
+
+	if (n > 10) {
+	    if (++i == 5) {
+		os << "<li>(... and " << (n - i) << " further failures)</li>";
+		break;
+	    }
+	}
+    }
+    os << "</ul>";
+
+    return os.str();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugincandidates.cpp	Thu Apr 14 16:52:19 2016 +0100
@@ -0,0 +1,235 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Copyright (c) 2016 Queen Mary, University of London
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+    Except as contained in this notice, the names of the Centre for
+    Digital Music and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#include "plugincandidates.h"
+
+#include <set>
+#include <stdexcept>
+#include <iostream>
+
+#include <QProcess>
+#include <QDir>
+#include <QTime>
+
+#if defined(_WIN32)
+#define PLUGIN_GLOB "*.dll"
+#elif defined(__APPLE__)
+#define PLUGIN_GLOB "*.dylib *.so"
+#else
+#define PLUGIN_GLOB "*.so"
+#endif
+
+using namespace std;
+
+PluginCandidates::PluginCandidates(string helperExecutableName) :
+    m_helper(helperExecutableName),
+    m_logCallback(0)
+{
+}
+
+void
+PluginCandidates::setLogCallback(LogCallback *cb)
+{
+    m_logCallback = cb;
+}
+
+vector<string>
+PluginCandidates::getCandidateLibrariesFor(string tag) const
+{
+    if (m_candidates.find(tag) == m_candidates.end()) return {};
+    else return m_candidates.at(tag);
+}
+
+vector<PluginCandidates::FailureRec>
+PluginCandidates::getFailedLibrariesFor(string tag) const
+{
+    if (m_failures.find(tag) == m_failures.end()) return {};
+    else return m_failures.at(tag);
+}
+
+void
+PluginCandidates::log(string message)
+{
+    if (m_logCallback) m_logCallback->log("PluginCandidates: " + message);
+}
+
+vector<string>
+PluginCandidates::getLibrariesInPath(vector<string> path)
+{
+    vector<string> candidates;
+
+    for (string dirname: path) {
+
+        log("scanning directory " + dirname + "\n");
+
+        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.toStdString());
+        }
+    }
+
+    return candidates;
+}
+
+void
+PluginCandidates::scan(string tag,
+		       vector<string> pluginPath,
+		       string descriptorSymbolName)
+{
+    vector<string> libraries = getLibrariesInPath(pluginPath);
+    vector<string> remaining = libraries;
+
+    int runlimit = 20;
+    int runcount = 0;
+    
+    vector<string> result;
+    
+    while (result.size() < libraries.size() && runcount < runlimit) {
+	vector<string> output = runHelper(remaining, descriptorSymbolName);
+	result.insert(result.end(), output.begin(), output.end());
+	int shortfall = int(remaining.size()) - int(output.size());
+	if (shortfall > 0) {
+	    // Helper bailed out for some reason presumably associated
+	    // with the plugin following the last one it reported
+	    // on. Add a failure entry for that one and continue with
+	    // the following ones.
+            string failed = *(remaining.rbegin() + shortfall - 1);
+            log("helper output ended before result for plugin " + failed + "\n");
+	    result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out");
+            remaining = vector<string>
+                (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
+	}
+	++runcount;
+    }
+
+    recordResult(tag, result);
+}
+
+vector<string>
+PluginCandidates::runHelper(vector<string> libraries, string descriptor)
+{
+    vector<string> output;
+
+    log("running helper with following library list:\n");
+    for (auto &lib: libraries) log(lib + "\n");
+
+    QProcess process;
+    process.setReadChannel(QProcess::StandardOutput);
+    process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
+    process.start(m_helper.c_str(), { descriptor.c_str() });
+    if (!process.waitForStarted()) {
+	cerr << "helper failed to start" << endl;
+	throw runtime_error("plugin load helper failed to start");
+    }
+    for (auto &lib: libraries) {
+	process.write(lib.c_str(), lib.size());
+	process.write("\n", 1);
+    }
+
+    QTime t;
+    t.start();
+    int timeout = 3000; // ms
+
+    int buflen = 4096;
+    bool done = false;
+    
+    while (!done) {
+	char buf[buflen];
+	qint64 linelen = process.readLine(buf, buflen);
+        if (linelen > 0) {
+            output.push_back(buf);
+            done = (output.size() == libraries.size());
+        } else if (linelen < 0) {
+            // error case
+            log("received error code while reading from helper\n");
+            done = true;
+	} else {
+            // no error, but no line read (could just be between
+            // lines, or could be eof)
+            done = (process.state() == QProcess::NotRunning);
+            if (!done) {
+                if (t.elapsed() > timeout) {
+                    // this is purely an emergency measure
+                    log("timeout: helper took too long, killing it\n");
+                    process.kill();
+                    done = true;
+                } else {
+                    process.waitForReadyRead(200);
+                }
+            }
+        }
+    }
+
+    if (process.state() != QProcess::NotRunning) {
+        process.close();
+        process.waitForFinished();
+    }
+	
+    return output;
+}
+
+void
+PluginCandidates::recordResult(string tag, vector<string> result)
+{
+    for (auto &r: result) {
+
+        QString s(r.c_str());
+        QStringList bits = s.split("|");
+
+        log("read output line from helper: " + r);
+        
+        if (bits.size() < 2 || bits.size() > 3) {
+            log("invalid output line (wrong number of |-separated fields)\n");
+            continue;
+        }
+
+        string status = bits[0].toStdString();
+        
+        string library = bits[1].toStdString();
+        if (bits.size() == 2) library = bits[1].trimmed().toStdString();
+
+        string message = "";
+        if (bits.size() > 2) message = bits[2].trimmed().toStdString();
+        
+        if (status == "SUCCESS") {
+            m_candidates[tag].push_back(library);
+
+        } else if (status == "FAILURE") {
+            m_failures[tag].push_back({ library, message });
+
+        } else {
+            log("unexpected status \"" + status + "\" in output line\n");
+        }
+    }
+}
+
--- a/vamp-plugin-load-checker.pro	Thu Apr 14 09:42:26 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-
-TEMPLATE = subdirs
-SUBDIRS = sub_checker_lib sub_checker sub_helper
-
-sub_checker_lib.file = checker-lib.pro
-sub_checker.file = checker.pro
-sub_helper.file = helper.pro
-