annotate PyPlugScanner.cpp @ 120:a38d318c85a9 tip

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