annotate src/plugincandidates.cpp @ 25:1eefc20919cd

Ah, this version should have been 2
author Chris Cannam
date Wed, 16 Nov 2016 16:24:12 +0000
parents 24b1d94440f5
children cf18645ff411
rev   line source
Chris@2 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@5 2 /*
Chris@19 3 Copyright (c) 2016 Queen Mary, University of London
Chris@5 4
Chris@19 5 Permission is hereby granted, free of charge, to any person
Chris@19 6 obtaining a copy of this software and associated documentation
Chris@19 7 files (the "Software"), to deal in the Software without
Chris@19 8 restriction, including without limitation the rights to use, copy,
Chris@19 9 modify, merge, publish, distribute, sublicense, and/or sell copies
Chris@19 10 of the Software, and to permit persons to whom the Software is
Chris@19 11 furnished to do so, subject to the following conditions:
Chris@5 12
Chris@19 13 The above copyright notice and this permission notice shall be
Chris@19 14 included in all copies or substantial portions of the Software.
Chris@5 15
Chris@19 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@19 17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@19 18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Chris@19 19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
Chris@19 20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
Chris@19 21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@19 22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@5 23
Chris@19 24 Except as contained in this notice, the names of the Centre for
Chris@19 25 Digital Music and Queen Mary, University of London shall not be
Chris@19 26 used in advertising or otherwise to promote the sale, use or other
Chris@19 27 dealings in this Software without prior written authorization.
Chris@5 28 */
Chris@2 29
Chris@2 30 #include "plugincandidates.h"
Chris@2 31
Chris@2 32 #include <set>
Chris@2 33 #include <stdexcept>
Chris@2 34 #include <iostream>
Chris@2 35
Chris@2 36 #include <QProcess>
Chris@2 37 #include <QDir>
Chris@6 38 #include <QTime>
Chris@2 39
Chris@4 40 #if defined(_WIN32)
Chris@2 41 #define PLUGIN_GLOB "*.dll"
Chris@4 42 #elif defined(__APPLE__)
Chris@2 43 #define PLUGIN_GLOB "*.dylib *.so"
Chris@2 44 #else
Chris@2 45 #define PLUGIN_GLOB "*.so"
Chris@2 46 #endif
Chris@2 47
Chris@2 48 using namespace std;
Chris@2 49
Chris@2 50 PluginCandidates::PluginCandidates(string helperExecutableName) :
Chris@6 51 m_helper(helperExecutableName),
Chris@6 52 m_logCallback(0)
Chris@2 53 {
Chris@2 54 }
Chris@2 55
Chris@6 56 void
Chris@6 57 PluginCandidates::setLogCallback(LogCallback *cb)
Chris@6 58 {
Chris@6 59 m_logCallback = cb;
Chris@6 60 }
Chris@6 61
Chris@2 62 vector<string>
Chris@4 63 PluginCandidates::getCandidateLibrariesFor(string tag) const
Chris@2 64 {
Chris@4 65 if (m_candidates.find(tag) == m_candidates.end()) return {};
Chris@4 66 else return m_candidates.at(tag);
Chris@2 67 }
Chris@2 68
Chris@2 69 vector<PluginCandidates::FailureRec>
Chris@4 70 PluginCandidates::getFailedLibrariesFor(string tag) const
Chris@2 71 {
Chris@4 72 if (m_failures.find(tag) == m_failures.end()) return {};
Chris@4 73 else return m_failures.at(tag);
Chris@2 74 }
Chris@2 75
Chris@6 76 void
Chris@6 77 PluginCandidates::log(string message)
Chris@6 78 {
Chris@24 79 if (m_logCallback) {
Chris@24 80 m_logCallback->log("PluginCandidates: " + message);
Chris@24 81 } else {
Chris@24 82 cerr << "PluginCandidates: " << message << endl;
Chris@24 83 }
Chris@6 84 }
Chris@6 85
Chris@2 86 vector<string>
Chris@2 87 PluginCandidates::getLibrariesInPath(vector<string> path)
Chris@2 88 {
Chris@2 89 vector<string> candidates;
Chris@2 90
Chris@2 91 for (string dirname: path) {
Chris@2 92
Chris@24 93 log("scanning directory " + dirname);
Chris@2 94
Chris@2 95 QDir dir(dirname.c_str(), PLUGIN_GLOB,
Chris@19 96 QDir::Name | QDir::IgnoreCase,
Chris@19 97 QDir::Files | QDir::Readable);
Chris@2 98
Chris@19 99 for (unsigned int i = 0; i < dir.count(); ++i) {
Chris@2 100 QString soname = dir.filePath(dir[i]);
Chris@11 101 // NB this means the library names passed to the helper
Chris@11 102 // are UTF-8 encoded
Chris@2 103 candidates.push_back(soname.toStdString());
Chris@2 104 }
Chris@2 105 }
Chris@2 106
Chris@2 107 return candidates;
Chris@2 108 }
Chris@2 109
Chris@2 110 void
Chris@2 111 PluginCandidates::scan(string tag,
Chris@19 112 vector<string> pluginPath,
Chris@19 113 string descriptorSymbolName)
Chris@2 114 {
Chris@2 115 vector<string> libraries = getLibrariesInPath(pluginPath);
Chris@2 116 vector<string> remaining = libraries;
Chris@2 117
Chris@2 118 int runlimit = 20;
Chris@2 119 int runcount = 0;
Chris@2 120
Chris@2 121 vector<string> result;
Chris@2 122
Chris@2 123 while (result.size() < libraries.size() && runcount < runlimit) {
Chris@19 124 vector<string> output = runHelper(remaining, descriptorSymbolName);
Chris@19 125 result.insert(result.end(), output.begin(), output.end());
Chris@19 126 int shortfall = int(remaining.size()) - int(output.size());
Chris@19 127 if (shortfall > 0) {
Chris@19 128 // Helper bailed out for some reason presumably associated
Chris@19 129 // with the plugin following the last one it reported
Chris@19 130 // on. Add a failure entry for that one and continue with
Chris@19 131 // the following ones.
Chris@6 132 string failed = *(remaining.rbegin() + shortfall - 1);
Chris@24 133 log("helper output ended before result for plugin " + failed);
Chris@19 134 result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out");
Chris@4 135 remaining = vector<string>
Chris@4 136 (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
Chris@19 137 }
Chris@19 138 ++runcount;
Chris@2 139 }
Chris@2 140
Chris@2 141 recordResult(tag, result);
Chris@2 142 }
Chris@2 143
Chris@2 144 vector<string>
Chris@2 145 PluginCandidates::runHelper(vector<string> libraries, string descriptor)
Chris@2 146 {
Chris@2 147 vector<string> output;
Chris@6 148
Chris@24 149 log("running helper " + m_helper + " with following library list:");
Chris@24 150 for (auto &lib: libraries) log(lib);
Chris@2 151
Chris@2 152 QProcess process;
Chris@2 153 process.setReadChannel(QProcess::StandardOutput);
Chris@4 154 process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
Chris@2 155 process.start(m_helper.c_str(), { descriptor.c_str() });
Chris@2 156 if (!process.waitForStarted()) {
cannam@13 157 QProcess::ProcessError err = process.error();
cannam@13 158 if (err == QProcess::FailedToStart) {
cannam@13 159 std::cerr << "Unable to start helper process " << m_helper
cannam@13 160 << std::endl;
cannam@13 161 } else if (err == QProcess::Crashed) {
cannam@13 162 std::cerr << "Helper process " << m_helper
cannam@13 163 << " crashed on startup" << std::endl;
cannam@13 164 } else {
cannam@13 165 std::cerr << "Helper process " << m_helper
cannam@13 166 << " failed on startup with error code "
cannam@13 167 << err << std::endl;
cannam@13 168 }
Chris@19 169 throw runtime_error("plugin load helper failed to start");
Chris@2 170 }
Chris@2 171 for (auto &lib: libraries) {
Chris@19 172 process.write(lib.c_str(), lib.size());
Chris@19 173 process.write("\n", 1);
Chris@2 174 }
Chris@2 175
Chris@6 176 QTime t;
Chris@6 177 t.start();
Chris@6 178 int timeout = 3000; // ms
Chris@6 179
Chris@12 180 const int buflen = 4096;
Chris@4 181 bool done = false;
Chris@4 182
Chris@4 183 while (!done) {
Chris@19 184 char buf[buflen];
Chris@19 185 qint64 linelen = process.readLine(buf, buflen);
Chris@4 186 if (linelen > 0) {
Chris@4 187 output.push_back(buf);
Chris@4 188 done = (output.size() == libraries.size());
Chris@4 189 } else if (linelen < 0) {
Chris@4 190 // error case
Chris@24 191 log("received error code while reading from helper");
Chris@4 192 done = true;
Chris@19 193 } else {
Chris@4 194 // no error, but no line read (could just be between
Chris@4 195 // lines, or could be eof)
Chris@4 196 done = (process.state() == QProcess::NotRunning);
Chris@6 197 if (!done) {
Chris@6 198 if (t.elapsed() > timeout) {
Chris@6 199 // this is purely an emergency measure
Chris@24 200 log("timeout: helper took too long, killing it");
Chris@6 201 process.kill();
Chris@6 202 done = true;
Chris@6 203 } else {
Chris@6 204 process.waitForReadyRead(200);
Chris@6 205 }
Chris@6 206 }
Chris@2 207 }
Chris@4 208 }
Chris@4 209
Chris@4 210 if (process.state() != QProcess::NotRunning) {
Chris@4 211 process.close();
Chris@4 212 process.waitForFinished();
Chris@4 213 }
cannam@13 214
Chris@24 215 log("helper completed");
cannam@13 216
Chris@2 217 return output;
Chris@2 218 }
Chris@2 219
Chris@2 220 void
Chris@2 221 PluginCandidates::recordResult(string tag, vector<string> result)
Chris@2 222 {
Chris@4 223 for (auto &r: result) {
Chris@4 224
Chris@4 225 QString s(r.c_str());
Chris@4 226 QStringList bits = s.split("|");
Chris@6 227
Chris@24 228 log(("read output line from helper: " + s.trimmed()).toStdString());
Chris@6 229
Chris@4 230 if (bits.size() < 2 || bits.size() > 3) {
Chris@24 231 log("invalid output line (wrong number of |-separated fields)");
Chris@4 232 continue;
Chris@4 233 }
Chris@4 234
Chris@4 235 string status = bits[0].toStdString();
Chris@4 236
Chris@4 237 string library = bits[1].toStdString();
Chris@4 238 if (bits.size() == 2) library = bits[1].trimmed().toStdString();
Chris@4 239
Chris@4 240 string message = "";
Chris@4 241 if (bits.size() > 2) message = bits[2].trimmed().toStdString();
Chris@4 242
Chris@4 243 if (status == "SUCCESS") {
Chris@4 244 m_candidates[tag].push_back(library);
Chris@4 245
Chris@4 246 } else if (status == "FAILURE") {
Chris@4 247 m_failures[tag].push_back({ library, message });
Chris@4 248
Chris@4 249 } else {
Chris@24 250 log("unexpected status \"" + status + "\" in output line");
Chris@4 251 }
Chris@4 252 }
Chris@2 253 }
Chris@2 254