Mercurial > hg > vamp-plugin-load-checker
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 -