view plugincandidates.cpp @ 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
line wrap: on
line source
/* -*- 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>

#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)
{
}

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);
}

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 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.
            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;
    }

    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.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);
    }

    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
            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;
}

void
PluginCandidates::recordResult(string tag, vector<string> result)
{
    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;
        }
    }
}