annotate PyPlugScanner.cpp @ 100:08dd88201115

Add numpy (is pip in path?)
author Chris Cannam
date Wed, 16 Jan 2019 15:47:57 +0000
parents 2f2292b029a4
children efff8e5d90a3
rev   line source
Chris@92 1 /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */
fazekasgy@37 2 /*
fazekasgy@37 3
fazekasgy@37 4 * Vampy : This plugin is a wrapper around the Vamp plugin API.
fazekasgy@37 5 * It allows for writing Vamp plugins in Python.
fazekasgy@37 6
fazekasgy@37 7 * Centre for Digital Music, Queen Mary University of London.
fazekasgy@37 8 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
fazekasgy@37 9 * for licence information.)
fazekasgy@37 10
fazekasgy@37 11 */
fazekasgy@37 12
fazekasgy@37 13
fazekasgy@37 14 #include "PyPlugScanner.h"
Chris@92 15 #include "PyExtensionManager.h"
Chris@92 16 #include "Debug.h"
fazekasgy@37 17 #include <algorithm>
fazekasgy@37 18 #include <cstdlib>
fazekasgy@37 19 //#include "vamp-hostsdk/PluginHostAdapter.h"
fazekasgy@37 20
fazekasgy@37 21 #ifdef _WIN32
fazekasgy@37 22 #include <windows.h>
fazekasgy@37 23 #include <tchar.h>
fazekasgy@37 24 #define pathsep ("\\")
fazekasgy@37 25 #else
fazekasgy@37 26 #include <dirent.h>
fazekasgy@37 27 #include <dlfcn.h>
fazekasgy@37 28 #define pathsep ("/")
fazekasgy@37 29 #endif
fazekasgy@37 30 #define joinPath(a,b) ( (a)+pathsep+(b) )
fazekasgy@37 31
fazekasgy@37 32 using std::string;
fazekasgy@37 33 using std::vector;
fazekasgy@37 34 using std::cerr;
fazekasgy@37 35 using std::endl;
fazekasgy@37 36 using std::find;
fazekasgy@37 37
fazekasgy@37 38 PyPlugScanner::PyPlugScanner()
fazekasgy@37 39 {
fazekasgy@37 40
fazekasgy@37 41 }
fazekasgy@37 42
fazekasgy@37 43 PyPlugScanner *PyPlugScanner::m_instance = NULL;
fazekasgy@37 44 bool PyPlugScanner::m_hasInstance = false;
fazekasgy@37 45
fazekasgy@37 46 PyPlugScanner*
fazekasgy@37 47 PyPlugScanner::getInstance()
fazekasgy@37 48 {
fazekasgy@37 49 if (!m_hasInstance) {
fazekasgy@37 50 m_instance = new PyPlugScanner();
fazekasgy@37 51 m_hasInstance = true;
fazekasgy@37 52 }
fazekasgy@37 53 return m_instance;
fazekasgy@37 54 }
fazekasgy@37 55
fazekasgy@37 56 void
fazekasgy@37 57 PyPlugScanner::setPath(vector<string> path)
fazekasgy@37 58 {
fazekasgy@37 59 m_path=path;
fazekasgy@37 60 }
fazekasgy@37 61
fazekasgy@37 62 // We assume that each script on the path has one valid class
fazekasgy@37 63 vector<string>
fazekasgy@37 64 PyPlugScanner::getPyPlugs()
fazekasgy@37 65 {
fazekasgy@37 66 //for_each m_path listFiles then return vector<pyPlugs>
fazekasgy@37 67 //key format: FullPathString/FileName.py:ClassName
fazekasgy@37 68
fazekasgy@37 69 bool getCompiled = true;
fazekasgy@37 70 char* getPyc = getenv("VAMPY_COMPILED");
fazekasgy@37 71 if (getPyc) {
fazekasgy@37 72 string value(getPyc);
fazekasgy@37 73 cerr << "VAMPY_COMPILED=" << value << endl;
fazekasgy@37 74 getCompiled = value.compare("1")?false:true;
fazekasgy@37 75 }
fazekasgy@37 76
fazekasgy@37 77 vector<string> pyPlugs;
fazekasgy@37 78 string pluginKey;
fazekasgy@37 79 PyObject *pyClass;
fazekasgy@37 80
fazekasgy@37 81 for (size_t i = 0; i < m_path.size(); ++i) {
fazekasgy@37 82
fazekasgy@37 83 vector<string> files = listFiles(m_path[i],"py");
fazekasgy@37 84
fazekasgy@37 85 /// recognise byte compiled plugins
fazekasgy@37 86 if (getCompiled) {
fazekasgy@58 87 vector<string> pyc_files = listFiles(m_path[i],"pyc");
fazekasgy@58 88 vector<string> pyo_files = listFiles(m_path[i],"pyo");
fazekasgy@58 89 mergeFileLists(pyc_files,pyo_files,".pyo");
fazekasgy@58 90 mergeFileLists(pyo_files,files,".py");
fazekasgy@37 91 }
fazekasgy@37 92
fazekasgy@37 93 for (vector<string>::iterator fi = files.begin();
fazekasgy@37 94 fi != files.end(); ++fi) {
fazekasgy@37 95 string script = *fi;
fazekasgy@37 96 if (!script.empty()) {
fazekasgy@37 97 string classname=script.substr(0,script.rfind('.'));
fazekasgy@37 98 pluginKey=joinPath(m_path[i],script)+":"+classname;
fazekasgy@37 99 pyClass = getScriptClass(m_path[i],classname);
fazekasgy@37 100 if (pyClass == NULL)
Chris@92 101 cerr << "Warning: Syntax error or other problem in scanning VamPy plugin: "
fazekasgy@37 102 << classname << ". Avoiding plugin." << endl;
fazekasgy@37 103 else {
fazekasgy@37 104 pyPlugs.push_back(pluginKey);
fazekasgy@37 105 m_pyClasses.push_back(pyClass);
Chris@92 106 }
fazekasgy@37 107 }
fazekasgy@37 108 }
fazekasgy@37 109 }
fazekasgy@37 110
fazekasgy@37 111 return pyPlugs;
fazekasgy@37 112
fazekasgy@37 113 }
fazekasgy@37 114
fazekasgy@37 115 /// insert python byte code names (.pyc) if a .py file can not be found
fazekasgy@37 116 /// The interpreter automatically generates byte code files and executes
fazekasgy@37 117 /// them if they exist. Therefore, we prefer .py files, but we allow
fazekasgy@37 118 /// (relatively) closed source distributions by recognising .pyc files.
fazekasgy@37 119 void
fazekasgy@58 120 PyPlugScanner::mergeFileLists(vector<string> &src, vector<string> &tg, string target_ext)
fazekasgy@37 121 {
fazekasgy@58 122 for (vector<string>::iterator srcit = src.begin();
fazekasgy@58 123 srcit != src.end(); ++srcit) {
fazekasgy@58 124 // cerr << *srcit;
fazekasgy@58 125 string src_name = *srcit;
fazekasgy@58 126 string tg_name = src_name.substr(0,src_name.rfind('.')) + target_ext;
fazekasgy@58 127 vector<string>::iterator tgit = find (tg.begin(), tg.end(), tg_name);
fazekasgy@58 128 if (tgit == tg.end()) tg.push_back(src_name);
fazekasgy@37 129 }
fazekasgy@37 130
fazekasgy@37 131 }
fazekasgy@37 132
fazekasgy@37 133
fazekasgy@37 134 //For now return one class object found in each script
fazekasgy@37 135 vector<PyObject*>
fazekasgy@37 136 PyPlugScanner::getPyClasses()
fazekasgy@37 137 {
fazekasgy@37 138 return m_pyClasses;
fazekasgy@37 139
fazekasgy@37 140 }
fazekasgy@37 141
fazekasgy@37 142 //Validate
fazekasgy@37 143 //This should not be called more than once!
fazekasgy@37 144 PyObject*
fazekasgy@37 145 PyPlugScanner::getScriptClass(string path, string classname)
fazekasgy@37 146 {
fazekasgy@37 147 //Add plugin path to active Python Path
fazekasgy@37 148 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
fazekasgy@37 149 PyRun_SimpleString(pyCmd.c_str());
fazekasgy@37 150
fazekasgy@37 151 //Assign an object to the source code
fazekasgy@37 152 PyObject *pySource = PyString_FromString(classname.c_str());
fazekasgy@37 153
fazekasgy@37 154 //Import it as a module into the py interpreter
fazekasgy@37 155 PyObject *pyModule = PyImport_Import(pySource);
fazekasgy@37 156 PyObject* pyError = PyErr_Occurred();
Chris@70 157 if (pyError) {
fazekasgy@37 158 cerr << "ERROR: error importing source: " << classname << endl;
fazekasgy@37 159 PyErr_Print();
fazekasgy@37 160 Py_DECREF(pySource);
fazekasgy@37 161 Py_CLEAR(pyModule); // safer if pyModule==NULL
fazekasgy@37 162 return NULL;
fazekasgy@37 163 }
fazekasgy@37 164 Py_DECREF(pySource);
fazekasgy@37 165
fazekasgy@37 166 //Read the dictionary object holding the namespace of the module (borrowed reference)
fazekasgy@37 167 PyObject *pyDict = PyModule_GetDict(pyModule);
fazekasgy@37 168 Py_DECREF(pyModule);
fazekasgy@37 169
fazekasgy@37 170 //Get the PluginClass from the module (borrowed reference)
fazekasgy@37 171 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
fazekasgy@37 172
Chris@92 173 if (pyClass == Py_None) {
Chris@92 174 DSTREAM << "Vampy: class name " << classname
Chris@92 175 << " is None in module; assuming it was scrubbed "
Chris@92 176 << "following an earlier load failure" << endl;
Chris@92 177 return NULL;
Chris@92 178 }
Chris@92 179
Chris@92 180 // Check if class is present and a callable method is implemented
Chris@92 181 if (!pyClass || !PyCallable_Check(pyClass)) {
fazekasgy@37 182 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
fazekasgy@37 183 << "Hint: plugin source filename and plugin class name must be the same." << endl;
fazekasgy@37 184 PyErr_Print();
fazekasgy@37 185 return NULL;
fazekasgy@37 186 }
Chris@92 187
Chris@92 188 bool acceptable = true;
Chris@92 189
Chris@92 190 // Check that the module doesn't have any name collisions with
Chris@92 191 // our own symbols
Chris@92 192
Chris@92 193 int i = 0;
Chris@92 194 while (PyExtensionManager::m_exposedNames[i]) {
Chris@92 195
Chris@92 196 const char* name = PyExtensionManager::m_exposedNames[i];
Chris@92 197 i++;
Chris@92 198
Chris@92 199 PyObject *item = PyDict_GetItemString(pyDict, name);
Chris@92 200 if (!item) continue;
Chris@92 201
Chris@92 202 if (item == Py_None) {
Chris@92 203 DSTREAM << "Vampy: name " << name << " is None "
Chris@92 204 << "in module " << classname
Chris@92 205 << "; assuming it was cleared on unload"
Chris@92 206 << endl;
Chris@92 207 continue;
Chris@92 208 }
Chris@92 209
Chris@92 210 PyTypeObject *metatype = Py_TYPE(item);
Chris@92 211
Chris@92 212 if (!strcmp(name, "frame2RealTime")) {
Chris@92 213 if (metatype != &PyCFunction_Type) {
Chris@92 214 cerr << "ERROR: plugin " << classname
Chris@92 215 << " redefines Vampy function name \""
Chris@92 216 << name << "\" (metatype is \""
Chris@92 217 << metatype->tp_name << "\")" << endl;
Chris@92 218 acceptable = false;
Chris@92 219 break;
Chris@92 220 } else {
Chris@92 221 continue;
Chris@92 222 }
Chris@92 223 }
Chris@92 224
Chris@92 225 if (metatype != &PyType_Type) {
Chris@92 226 cerr << "ERROR: plugin " << classname
Chris@92 227 << " uses Vampy reserved type name \"" << name
Chris@92 228 << "\" for non-type (metatype is \""
Chris@92 229 << metatype->tp_name << "\")" << endl;
Chris@92 230 acceptable = false;
Chris@92 231 break;
Chris@92 232 }
Chris@92 233
Chris@92 234 PyTypeObject *type = (PyTypeObject *)item;
Chris@92 235 if (type->tp_name == std::string("vampy.") + name) {
Chris@92 236 DSTREAM << "Vampy: acceptable Vampy type name "
Chris@92 237 << type->tp_name << " found in module" << endl;
Chris@92 238 } else {
Chris@92 239 cerr << "ERROR: plugin " << classname
Chris@92 240 << " redefines Vampy type \"" << name << "\"";
Chris@92 241 if (strcmp(type->tp_name, name)) {
Chris@92 242 cerr << " (as \"" << type->tp_name << "\")";
Chris@92 243 }
Chris@92 244 cerr << endl;
Chris@92 245 acceptable = false;
Chris@92 246 break;
Chris@92 247 }
Chris@92 248 }
Chris@92 249
Chris@92 250 if (acceptable) {
Chris@92 251 return pyClass;
Chris@92 252 } else {
Chris@92 253 PyObject *key = PyString_FromString(classname.c_str());
Chris@92 254 PyDict_SetItem(pyDict, key, Py_None);
Chris@92 255 Py_DECREF(key);
Chris@92 256 return NULL;
Chris@92 257 }
fazekasgy@37 258 }
fazekasgy@37 259
fazekasgy@37 260
fazekasgy@37 261
fazekasgy@37 262 // Return a list of files in dir with given extension
fazekasgy@37 263 // Code taken from hostext/PluginLoader.cpp
fazekasgy@37 264 vector<string>
fazekasgy@37 265 PyPlugScanner::listFiles(string dir, string extension)
fazekasgy@37 266 {
fazekasgy@37 267 vector<string> files;
fazekasgy@37 268
fazekasgy@37 269 #ifdef _WIN32
fazekasgy@37 270
fazekasgy@37 271 string expression = dir + "\\*." + extension;
fazekasgy@37 272 WIN32_FIND_DATA data;
fazekasgy@37 273 HANDLE fh = FindFirstFile(expression.c_str(), &data);
fazekasgy@37 274 if (fh == INVALID_HANDLE_VALUE) return files;
fazekasgy@37 275
fazekasgy@37 276 bool ok = true;
fazekasgy@37 277 while (ok) {
fazekasgy@37 278 files.push_back(data.cFileName);
fazekasgy@37 279 ok = FindNextFile(fh, &data);
fazekasgy@37 280 }
fazekasgy@37 281
fazekasgy@37 282 FindClose(fh);
fazekasgy@37 283
fazekasgy@37 284 #else
fazekasgy@37 285
fazekasgy@37 286 size_t extlen = extension.length();
fazekasgy@37 287 DIR *d = opendir(dir.c_str());
fazekasgy@37 288 if (!d) return files;
fazekasgy@37 289
fazekasgy@37 290 struct dirent *e = 0;
fazekasgy@37 291 while ((e = readdir(d))) {
fazekasgy@37 292 size_t len = strlen(e->d_name);
fazekasgy@37 293 if (len < extlen + 2 ||
fazekasgy@37 294 e->d_name + len - extlen - 1 != "." + extension) {
fazekasgy@37 295 continue;
fazekasgy@37 296 }
fazekasgy@37 297 files.push_back(e->d_name);
fazekasgy@37 298 }
fazekasgy@37 299
fazekasgy@37 300 closedir(d);
fazekasgy@37 301 #endif
fazekasgy@37 302
fazekasgy@37 303 return files;
fazekasgy@37 304 }
fazekasgy@37 305
fazekasgy@37 306
fazekasgy@37 307 //!!! It would probably be better to actually call
fazekasgy@37 308 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
fazekasgy@37 309 // needs to link against vamp-hostsdk, but that's probably acceptable
fazekasgy@37 310 // as it is sort of a host as well.
fazekasgy@37 311
fazekasgy@37 312 // std::vector<std::string>
fazekasgy@37 313 // PyPlugScanner::getAllValidPath()
fazekasgy@37 314 // {
fazekasgy@37 315 // Vamp::PluginHostAdapter host_adapter( ??? );
fazekasgy@37 316 // return host_adapter.getPluginPath();
fazekasgy@37 317 // }
fazekasgy@37 318
fazekasgy@37 319 // tried to implement it, but found a bit confusing how to
fazekasgy@37 320 // instantiate the host adapter here...
fazekasgy@37 321
fazekasgy@37 322
fazekasgy@37 323 //Return correct plugin directories as per platform
fazekasgy@37 324 //Code taken from vamp-sdk/PluginHostAdapter.cpp
fazekasgy@37 325 std::vector<std::string>
fazekasgy@37 326 PyPlugScanner::getAllValidPath()
fazekasgy@37 327 {
fazekasgy@37 328 std::vector<std::string> path;
fazekasgy@37 329 std::string envPath;
fazekasgy@37 330
Chris@97 331 bool nonNative32 = false;
Chris@97 332 #ifdef _WIN32
Chris@97 333 BOOL (WINAPI *fnIsWow64Process)(HANDLE, PBOOL) =
Chris@97 334 (BOOL (WINAPI *)(HANDLE, PBOOL)) GetProcAddress
Chris@97 335 (GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
Chris@97 336 if (fnIsWow64Process) {
Chris@97 337 BOOL wow64 = FALSE;
Chris@97 338 if (fnIsWow64Process(GetCurrentProcess(), &wow64) && wow64) {
Chris@97 339 nonNative32 = true;
Chris@97 340 }
Chris@97 341 }
Chris@97 342 #endif
Chris@97 343
Chris@97 344 char *cpath;
Chris@97 345 if (nonNative32) {
Chris@97 346 cpath = getenv("VAMP_PATH_32");
Chris@97 347 } else {
Chris@97 348 cpath = getenv("VAMP_PATH");
Chris@97 349 }
fazekasgy@37 350 if (cpath) envPath = cpath;
fazekasgy@37 351
fazekasgy@37 352 #ifdef _WIN32
fazekasgy@37 353 #define PATH_SEPARATOR ';'
fazekasgy@37 354 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
fazekasgy@37 355 #else
fazekasgy@37 356 #define PATH_SEPARATOR ':'
fazekasgy@37 357 #ifdef __APPLE__
fazekasgy@37 358 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
fazekasgy@37 359 #else
fazekasgy@37 360 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
fazekasgy@37 361 #endif
fazekasgy@37 362 #endif
fazekasgy@37 363
fazekasgy@37 364 if (envPath == "") {
fazekasgy@37 365 envPath = DEFAULT_VAMP_PATH;
fazekasgy@37 366 char *chome = getenv("HOME");
fazekasgy@37 367 if (chome) {
fazekasgy@37 368 std::string home(chome);
fazekasgy@37 369 std::string::size_type f;
fazekasgy@37 370 while ((f = envPath.find("$HOME")) != std::string::npos &&
fazekasgy@37 371 f < envPath.length()) {
fazekasgy@37 372 envPath.replace(f, 5, home);
fazekasgy@37 373 }
fazekasgy@37 374 }
fazekasgy@37 375 #ifdef _WIN32
fazekasgy@37 376 char *cpfiles = getenv("ProgramFiles");
fazekasgy@37 377 if (!cpfiles) cpfiles = "C:\\Program Files";
fazekasgy@37 378 std::string pfiles(cpfiles);
fazekasgy@37 379 std::string::size_type f;
fazekasgy@37 380 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
fazekasgy@37 381 f < envPath.length()) {
fazekasgy@37 382 envPath.replace(f, 14, pfiles);
fazekasgy@37 383 }
fazekasgy@37 384 #endif
fazekasgy@37 385 }
fazekasgy@37 386
fazekasgy@37 387 std::string::size_type index = 0, newindex = 0;
fazekasgy@37 388
fazekasgy@37 389 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
fazekasgy@37 390 path.push_back(envPath.substr(index, newindex - index));
fazekasgy@37 391 index = newindex + 1;
fazekasgy@37 392 }
fazekasgy@37 393
fazekasgy@37 394 path.push_back(envPath.substr(index));
fazekasgy@37 395
fazekasgy@37 396 //can add an extra path for vampy plugins
fazekasgy@37 397 char* extraPath = getenv("VAMPY_EXTPATH");
fazekasgy@37 398 if (extraPath) {
fazekasgy@37 399 string vampyPath(extraPath);
fazekasgy@37 400 cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
fazekasgy@37 401 path.push_back(vampyPath);
fazekasgy@37 402 }
fazekasgy@37 403
fazekasgy@37 404 return path;
fazekasgy@37 405 }