changeset 4:6f891a9c6434

Make checker with hard-coded knowledge about various plugin types and paths; fix some process management problems
author Chris Cannam
date Wed, 13 Apr 2016 12:00:07 +0100
parents 3bae396cf8e0
children 74064d6f5e07
files checker.cpp checker.pro helper.cpp knownplugins.cpp knownplugins.h plugincandidates.cpp plugincandidates.h
diffstat 7 files changed, 331 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/checker.cpp	Wed Apr 13 09:12:04 2016 +0100
+++ b/checker.cpp	Wed Apr 13 12:00:07 2016 +0100
@@ -1,12 +1,23 @@
 
-#include "plugincandidates.h"
+#include "knownplugins.h"
 
-int main(int argc, char **argv)
+#include <iostream>
+
+using namespace std;
+
+int main(int, char **)
 {
-    //!!! just a test
-    PluginCandidates candidates("./helper");
-    candidates.scan("vamp",
-                    { "/usr/lib/vamp", "/usr/local/lib/vamp" },
-                    "vampGetPluginDescriptor");
+    KnownPlugins kp;
+
+    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.pro	Wed Apr 13 09:12:04 2016 +0100
+++ b/checker.pro	Wed Apr 13 12:00:07 2016 +0100
@@ -1,23 +1,27 @@
 
 TEMPLATE = app
 
-CONFIG += qt stl c++11 exceptions console
+CONFIG += qt stl c++11 exceptions console warn_on
 QT -= xml network gui widgets
 
 # 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
 
+QMAKE_CXXFLAGS += -Werror
+
 TARGET = checker
 
 OBJECTS_DIR = o
 MOC_DIR = o
 
 HEADERS += \
-	plugincandidates.h
+	plugincandidates.h \
+	knownplugins.h
 
 SOURCES += \
 	plugincandidates.cpp \
+	knownplugins.cpp \
 	checker.cpp
 
 QMAKE_POST_LINK=make -f Makefile.helper
--- a/helper.cpp	Wed Apr 13 09:12:04 2016 +0100
+++ b/helper.cpp	Wed Apr 13 12:00:07 2016 +0100
@@ -3,10 +3,10 @@
 /**
  * Plugin Load Checker Helper
  *
- * This program accepts the name of a descriptor function as its only
+ * 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 function, printing a
+ * 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
@@ -57,17 +57,23 @@
 
 string check(string soname, string descriptor)
 {
+//    cerr << "helper: trying: " << soname << endl;
+    
     void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
     if (!handle) {
+//	cerr << "helper: failed to open" << endl;
 	return "Unable to open plugin library: " + error();
     }
 
     void *fn = DLSYM(handle, descriptor);
     if (!fn) {
+//	cerr << "helper: failed to find descriptor" << endl;
 	return "Failed to find plugin descriptor " + descriptor +
 	    " in library: " + error();
     }
 
+//    cerr << "helper: succeeded" << endl;
+    
     return "";
 }
 
@@ -78,7 +84,7 @@
 
     if (argc != 2) {
 	cerr << "\nUsage:\n    " << argv[0] << " descriptorname\n"
-	    "\nwhere descriptorname is the name of a plugin descriptor function to be sought\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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/knownplugins.cpp	Wed Apr 13 12:00:07 2016 +0100
@@ -0,0 +1,172 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include "knownplugins.h"
+
+#include <sstream>
+
+using namespace std;
+
+#if defined(_WIN32)
+#define PATH_SEPARATOR ';'
+#else
+#define PATH_SEPARATOR ':'
+#endif
+
+KnownPlugins::KnownPlugins() :
+    m_candidates("./helper") //!!!??? wot to do
+{
+    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/knownplugins.h	Wed Apr 13 12:00:07 2016 +0100
@@ -0,0 +1,53 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#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::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	Wed Apr 13 09:12:04 2016 +0100
+++ b/plugincandidates.cpp	Wed Apr 13 12:00:07 2016 +0100
@@ -9,17 +9,13 @@
 #include <QProcess>
 #include <QDir>
 
-#ifdef _WIN32
+#if defined(_WIN32)
 #define PLUGIN_GLOB "*.dll"
-#define PATH_SEPARATOR ';'
-#else
-#define PATH_SEPARATOR ':'
-#ifdef __APPLE__
+#elif defined(__APPLE__)
 #define PLUGIN_GLOB "*.dylib *.so"
 #else
 #define PLUGIN_GLOB "*.so"
 #endif
-#endif
 
 using namespace std;
 
@@ -29,15 +25,17 @@
 }
 
 vector<string>
-PluginCandidates::getCandidateLibrariesFor(string tag)
+PluginCandidates::getCandidateLibrariesFor(string tag) const
 {
-    return m_candidates[tag];
+    if (m_candidates.find(tag) == m_candidates.end()) return {};
+    else return m_candidates.at(tag);
 }
 
 vector<PluginCandidates::FailureRec>
-PluginCandidates::getFailedLibrariesFor(string tag)
+PluginCandidates::getFailedLibrariesFor(string tag) const
 {
-    return m_failures[tag];
+    if (m_failures.find(tag) == m_failures.end()) return {};
+    else return m_failures.at(tag);
 }
 
 vector<string>
@@ -67,7 +65,7 @@
 void
 PluginCandidates::scan(string tag,
 		       vector<string> pluginPath,
-		       string descriptorFunctionName)
+		       string descriptorSymbolName)
 {
     vector<string> libraries = getLibrariesInPath(pluginPath);
     vector<string> remaining = libraries;
@@ -78,21 +76,20 @@
     vector<string> result;
     
     while (result.size() < libraries.size() && runcount < runlimit) {
-	vector<string> output = runHelper(remaining, descriptorFunctionName);
+	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 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);
-	    }
+	    // on. Add a failure entry for that one and continue with
+	    // the following ones.
+            cerr << "shortfall = " << shortfall << " (of " << remaining.size() << ")" << endl;
+	    result.push_back("FAILURE|" +
+                             *(remaining.rbegin() + shortfall - 1) +
+                             "|Plugin load check failed");
+            remaining = vector<string>
+                (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
 	}
 	++runcount;
     }
@@ -104,11 +101,12 @@
 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;
+//    cerr << "running helper with following library list:" << endl;
+//    for (auto &lib: libraries) cerr << lib << endl;
 
     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;
@@ -120,21 +118,29 @@
     }
 
     int buflen = 4096;
-    while (process.waitForReadyRead()) {
+    bool done = false;
+    
+    while (!done) {
 	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;
+        if (linelen > 0) {
+            output.push_back(buf);
+            done = (output.size() == libraries.size());
+        } else if (linelen < 0) {
+            // error case
+            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) process.waitForReadyRead(100);
         }
-    }	
+    }
+
+    if (process.state() != QProcess::NotRunning) {
+        process.close();
+        process.waitForFinished();
+    }
 	
     return output;
 }
@@ -142,8 +148,33 @@
 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;
+    for (auto &r: result) {
+
+        QString s(r.c_str());
+        QStringList bits = s.split("|");
+        if (bits.size() < 2 || bits.size() > 3) {
+            cerr << "Invalid helper output line: \"" << r << "\"" << endl;
+            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 {
+            cerr << "Unexpected status " << status
+                 << " in helper output line: \"" << r << "\"" << endl;
+        }
+    }
 }
 
--- a/plugincandidates.h	Wed Apr 13 09:12:04 2016 +0100
+++ b/plugincandidates.h	Wed Apr 13 12:00:07 2016 +0100
@@ -28,7 +28,7 @@
 
     /** 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
+     *  symbol can be looked up in each. Store the results
      *  internally, associated with the given (arbitrary) tag, for
      *  later querying using getCandidateLibrariesFor() and
      *  getFailedLibrariesFor().
@@ -37,12 +37,12 @@
      */
     void scan(std::string tag,
 	      stringlist pluginPath,
-	      std::string descriptorFunctionName);
+	      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);
+    stringlist getCandidateLibrariesFor(std::string tag) const;
 
     struct FailureRec {
 	std::string library;
@@ -52,7 +52,7 @@
     /** Return list of failure reports arising from the prior scan for
      *  the given tag. 
      */
-    std::vector<FailureRec> getFailedLibrariesFor(std::string tag);
+    std::vector<FailureRec> getFailedLibrariesFor(std::string tag) const;
 
 private:
     std::string m_helper;