changeset 2:2288c1d05c28

Simple Qt-based class to invoke the helper
author Chris Cannam
date Tue, 12 Apr 2016 17:38:57 +0100
parents fbffc249990c
children 3bae396cf8e0
files Makefile helper.cpp plugincandidates.cpp plugincandidates.h
diffstat 4 files changed, 240 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Tue Apr 12 13:33:00 2016 +0100
+++ b/Makefile	Tue Apr 12 17:38:57 2016 +0100
@@ -1,9 +1,17 @@
+
+all:	helper plugincandidates
 
 CXXFLAGS	:= -Wall -Werror 
 
 helper:	helper.o
 		$(CXX) -o $@ $< -ldl
 
+# todo: qmake .pro file
+CXXFLAGS	+= -I/usr/include/qt -I/usr/include/qt/QtCore -fPIC -std=c++11
+
+plugincandidates:	plugincandidates.o
+		$(CXX) -o $@ $< -lQt5Core -ldl
+
 clean:
-		rm helper.o
+		rm helper.o plugincandidates.o
 
--- a/helper.cpp	Tue Apr 12 13:33:00 2016 +0100
+++ b/helper.cpp	Tue Apr 12 17:38:57 2016 +0100
@@ -1,3 +1,4 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
 
 /**
  * Plugin Load Checker Helper
@@ -20,11 +21,11 @@
  * Output line for failed load of library libname.so:
  * FAILURE|/path/to/libname.so|Reason for failure if available
  *
- * Note that 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.
+ * 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.
  */
 
 #ifdef _WIN32
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugincandidates.cpp	Tue Apr 12 17:38:57 2016 +0100
@@ -0,0 +1,158 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include "plugincandidates.h"
+
+#include <set>
+#include <stdexcept>
+#include <iostream>
+
+#include <QProcess>
+#include <QDir>
+
+#ifdef _WIN32
+#define PLUGIN_GLOB "*.dll"
+#define PATH_SEPARATOR ';'
+#else
+#define PATH_SEPARATOR ':'
+#ifdef __APPLE__
+#define PLUGIN_GLOB "*.dylib *.so"
+#else
+#define PLUGIN_GLOB "*.so"
+#endif
+#endif
+
+using namespace std;
+
+PluginCandidates::PluginCandidates(string helperExecutableName) :
+    m_helper(helperExecutableName)
+{
+}
+
+vector<string>
+PluginCandidates::getCandidateLibrariesFor(string tag)
+{
+    return m_candidates[tag];
+}
+
+vector<PluginCandidates::FailureRec>
+PluginCandidates::getFailedLibrariesFor(string tag)
+{
+    return m_failures[tag];
+}
+
+vector<string>
+PluginCandidates::getLibrariesInPath(vector<string> path)
+{
+    vector<string> candidates;
+
+    for (string dirname: path) {
+
+//#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "getLibrariesInPath: scanning directory " << dirname << endl;
+//#endif
+
+        QDir dir(dirname.c_str(), PLUGIN_GLOB,
+		 QDir::Name | QDir::IgnoreCase,
+		 QDir::Files | QDir::Readable);
+
+	for (unsigned int i = 0; i < dir.count(); ++i) {
+            QString soname = dir.filePath(dir[i]);
+            candidates.push_back(soname.toStdString());
+        }
+    }
+
+    return candidates;
+}
+
+void
+PluginCandidates::scan(string tag,
+		       vector<string> pluginPath,
+		       string descriptorFunctionName)
+{
+    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, descriptorFunctionName);
+	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 null entry for that one and continue with the
+	    // following ones.
+	    result.push_back("");
+	    if (shortfall == 1) {
+		remaining = vector<string>();
+	    } else {
+		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;
+    cerr << "running helper with following library list:" << endl;
+    for (auto &lib: libraries) cerr << lib << endl;
+
+    QProcess process;
+    process.setReadChannel(QProcess::StandardOutput);
+    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);
+    }
+
+    int buflen = 4096;
+    while (process.waitForReadyRead()) {
+	char buf[buflen];
+	qint64 linelen = process.readLine(buf, buflen);
+//        cerr << "read line: " << buf;
+	if (linelen < 0) {
+	    cerr << "read failed from plugin load helper" << endl;
+	    return output;
+	}
+	output.push_back(buf);
+        if (output.size() == libraries.size()) {
+            process.close();
+            process.waitForFinished();
+            break;
+        }
+    }	
+	
+    return output;
+}
+
+void
+PluginCandidates::recordResult(string tag, vector<string> result)
+{
+    cerr << "recordResult: not yet implemented, but result was:" << endl;
+    for (auto &r: result) cerr << r;
+    cerr << "(ends)" << endl;
+}
+
+int main(int argc, char **argv)
+{
+    //!!! just a test
+    PluginCandidates candidates("./helper");
+    candidates.scan("vamp",
+                    { "/usr/lib/vamp", "/usr/local/lib/vamp" },
+                    "vampGetPluginDescriptor");
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugincandidates.h	Tue Apr 12 17:38:57 2016 +0100
@@ -0,0 +1,67 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#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);
+
+    /** Scan the libraries found in the given plugin path (i.e. list
+     *  of plugin directories), checking that the given descriptor
+     *  function 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 descriptorFunctionName);
+
+    /** Return list of plugin library paths that were checked
+     *  successfully during the scan for the given tag.
+     */
+    stringlist getCandidateLibrariesFor(std::string tag);
+
+    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);
+
+private:
+    std::string m_helper;
+    std::map<std::string, stringlist> m_candidates;
+    std::map<std::string, std::vector<FailureRec> > m_failures;
+
+    stringlist getLibrariesInPath(stringlist path);
+    stringlist runHelper(stringlist libraries, std::string descriptor);
+    void recordResult(std::string tag, stringlist results);
+};
+
+#endif