annotate PyPlugScanner.cpp @ 53:7e59caea821b

* Make a better job of preloading Python, especially when it's in a framework. Go for the Python file in the frameworks directory in preference to any libpythonX.Y.dylib. Particularly, don't try to preload any library without an absolute path until we've exhausted all our framework possibilities (so as to avoid picking up an ancient system library).
author cannam
date Fri, 09 Oct 2009 13:48:25 +0000
parents 27bab3a16c9a
children 62dcaa5fe6f8
rev   line source
fazekasgy@37 1 /*
fazekasgy@37 2
fazekasgy@37 3 * Vampy : This plugin is a wrapper around the Vamp plugin API.
fazekasgy@37 4 * It allows for writing Vamp plugins in Python.
fazekasgy@37 5
fazekasgy@37 6 * Centre for Digital Music, Queen Mary University of London.
fazekasgy@37 7 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
fazekasgy@37 8 * for licence information.)
fazekasgy@37 9
fazekasgy@37 10 */
fazekasgy@37 11
fazekasgy@37 12
fazekasgy@37 13 #include "PyPlugScanner.h"
fazekasgy@37 14 #include <algorithm>
fazekasgy@37 15 #include <cstdlib>
fazekasgy@37 16 //#include "vamp-hostsdk/PluginHostAdapter.h"
fazekasgy@37 17
fazekasgy@37 18 #ifdef _WIN32
fazekasgy@37 19 #include <windows.h>
fazekasgy@37 20 #include <tchar.h>
fazekasgy@37 21 #define pathsep ("\\")
fazekasgy@37 22 #else
fazekasgy@37 23 #include <dirent.h>
fazekasgy@37 24 #include <dlfcn.h>
fazekasgy@37 25 #define pathsep ("/")
fazekasgy@37 26 #endif
fazekasgy@37 27 #define joinPath(a,b) ( (a)+pathsep+(b) )
fazekasgy@37 28
fazekasgy@37 29 using std::string;
fazekasgy@37 30 using std::vector;
fazekasgy@37 31 using std::cerr;
fazekasgy@37 32 using std::endl;
fazekasgy@37 33 using std::find;
fazekasgy@37 34
fazekasgy@37 35 PyPlugScanner::PyPlugScanner()
fazekasgy@37 36 {
fazekasgy@37 37
fazekasgy@37 38 }
fazekasgy@37 39
fazekasgy@37 40 PyPlugScanner *PyPlugScanner::m_instance = NULL;
fazekasgy@37 41 bool PyPlugScanner::m_hasInstance = false;
fazekasgy@37 42
fazekasgy@37 43 PyPlugScanner*
fazekasgy@37 44 PyPlugScanner::getInstance()
fazekasgy@37 45 {
fazekasgy@37 46 if (!m_hasInstance) {
fazekasgy@37 47 m_instance = new PyPlugScanner();
fazekasgy@37 48 m_hasInstance = true;
fazekasgy@37 49 }
fazekasgy@37 50 return m_instance;
fazekasgy@37 51 }
fazekasgy@37 52
fazekasgy@37 53 void
fazekasgy@37 54 PyPlugScanner::setPath(vector<string> path)
fazekasgy@37 55 {
fazekasgy@37 56 m_path=path;
fazekasgy@37 57 }
fazekasgy@37 58
fazekasgy@37 59 // We assume that each script on the path has one valid class
fazekasgy@37 60 vector<string>
fazekasgy@37 61 PyPlugScanner::getPyPlugs()
fazekasgy@37 62 {
fazekasgy@37 63 //for_each m_path listFiles then return vector<pyPlugs>
fazekasgy@37 64 //key format: FullPathString/FileName.py:ClassName
fazekasgy@37 65
fazekasgy@37 66 bool getCompiled = true;
fazekasgy@37 67 char* getPyc = getenv("VAMPY_COMPILED");
fazekasgy@37 68 if (getPyc) {
fazekasgy@37 69 string value(getPyc);
fazekasgy@37 70 cerr << "VAMPY_COMPILED=" << value << endl;
fazekasgy@37 71 getCompiled = value.compare("1")?false:true;
fazekasgy@37 72 }
fazekasgy@37 73
fazekasgy@37 74 vector<string> pyPlugs;
fazekasgy@37 75 string pluginKey;
fazekasgy@37 76 PyObject *pyClass;
fazekasgy@37 77
fazekasgy@37 78 for (size_t i = 0; i < m_path.size(); ++i) {
fazekasgy@37 79
fazekasgy@37 80 vector<string> files = listFiles(m_path[i],"py");
fazekasgy@37 81
fazekasgy@37 82 /// recognise byte compiled plugins
fazekasgy@37 83 if (getCompiled) {
fazekasgy@37 84 vector<string> compiled_files = listFiles(m_path[i],"pyc");
fazekasgy@37 85 mergeFileLists(compiled_files,files);
fazekasgy@37 86 }
fazekasgy@37 87
fazekasgy@37 88 for (vector<string>::iterator fi = files.begin();
fazekasgy@37 89 fi != files.end(); ++fi) {
fazekasgy@37 90 string script = *fi;
fazekasgy@37 91 if (!script.empty()) {
fazekasgy@37 92 string classname=script.substr(0,script.rfind('.'));
fazekasgy@37 93 pluginKey=joinPath(m_path[i],script)+":"+classname;
fazekasgy@37 94 pyClass = getScriptClass(m_path[i],classname);
fazekasgy@37 95 if (pyClass == NULL)
fazekasgy@37 96 cerr << "Warning: Syntax error in VamPy plugin: "
fazekasgy@37 97 << classname << ". Avoiding plugin." << endl;
fazekasgy@37 98 else {
fazekasgy@37 99 pyPlugs.push_back(pluginKey);
fazekasgy@37 100 m_pyClasses.push_back(pyClass);
fazekasgy@37 101 }
fazekasgy@37 102 //pyPlugs.push_back(pluginKey);
fazekasgy@37 103 }
fazekasgy@37 104 }
fazekasgy@37 105 }
fazekasgy@37 106
fazekasgy@37 107 return pyPlugs;
fazekasgy@37 108
fazekasgy@37 109 }
fazekasgy@37 110
fazekasgy@37 111 /// insert python byte code names (.pyc) if a .py file can not be found
fazekasgy@37 112 /// The interpreter automatically generates byte code files and executes
fazekasgy@37 113 /// them if they exist. Therefore, we prefer .py files, but we allow
fazekasgy@37 114 /// (relatively) closed source distributions by recognising .pyc files.
fazekasgy@37 115 void
fazekasgy@37 116 PyPlugScanner::mergeFileLists(vector<string> &pyc, vector<string> &py)
fazekasgy@37 117 {
fazekasgy@37 118 for (vector<string>::iterator pycit = pyc.begin();
fazekasgy@37 119 pycit != pyc.end(); ++pycit) {
fazekasgy@37 120 // cerr << *pycit;
fazekasgy@37 121 string pyc_name = *pycit;
fazekasgy@37 122 string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py";
fazekasgy@37 123 vector<string>::iterator pyit = find (py.begin(), py.end(), py_name);
fazekasgy@37 124 if (pyit == py.end()) py.push_back(pyc_name);
fazekasgy@37 125 }
fazekasgy@37 126
fazekasgy@37 127 }
fazekasgy@37 128
fazekasgy@37 129
fazekasgy@37 130 //For now return one class object found in each script
fazekasgy@37 131 vector<PyObject*>
fazekasgy@37 132 PyPlugScanner::getPyClasses()
fazekasgy@37 133 {
fazekasgy@37 134 return m_pyClasses;
fazekasgy@37 135
fazekasgy@37 136 }
fazekasgy@37 137
fazekasgy@37 138 //Validate
fazekasgy@37 139 //This should not be called more than once!
fazekasgy@37 140 PyObject*
fazekasgy@37 141 PyPlugScanner::getScriptClass(string path, string classname)
fazekasgy@37 142 {
fazekasgy@37 143
fazekasgy@37 144 //Add plugin path to active Python Path
fazekasgy@37 145 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
fazekasgy@37 146 PyRun_SimpleString(pyCmd.c_str());
fazekasgy@37 147
fazekasgy@37 148 //Assign an object to the source code
fazekasgy@37 149 PyObject *pySource = PyString_FromString(classname.c_str());
fazekasgy@37 150
fazekasgy@37 151 //Import it as a module into the py interpreter
fazekasgy@37 152 PyObject *pyModule = PyImport_Import(pySource);
fazekasgy@37 153 PyObject* pyError = PyErr_Occurred();
fazekasgy@37 154 if (! pyError == 0) {
fazekasgy@37 155 cerr << "ERROR: error importing source: " << classname << endl;
fazekasgy@37 156 PyErr_Print();
fazekasgy@37 157 Py_DECREF(pySource);
fazekasgy@37 158 Py_CLEAR(pyModule); // safer if pyModule==NULL
fazekasgy@37 159 return NULL;
fazekasgy@37 160 }
fazekasgy@37 161 Py_DECREF(pySource);
fazekasgy@37 162
fazekasgy@37 163 //Read the dictionary object holding the namespace of the module (borrowed reference)
fazekasgy@37 164 PyObject *pyDict = PyModule_GetDict(pyModule);
fazekasgy@37 165 Py_DECREF(pyModule);
fazekasgy@37 166
fazekasgy@37 167 //Get the PluginClass from the module (borrowed reference)
fazekasgy@37 168 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
fazekasgy@37 169
fazekasgy@37 170 //Check if class is present and a callable method is implemented
fazekasgy@37 171 if (pyClass && PyCallable_Check(pyClass)) {
fazekasgy@37 172
fazekasgy@37 173 return pyClass;
fazekasgy@37 174 }
fazekasgy@37 175 else {
fazekasgy@37 176 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
fazekasgy@37 177 << "Hint: plugin source filename and plugin class name must be the same." << endl;
fazekasgy@37 178 PyErr_Print();
fazekasgy@37 179 return NULL;
fazekasgy@37 180 }
fazekasgy@37 181 }
fazekasgy@37 182
fazekasgy@37 183
fazekasgy@37 184
fazekasgy@37 185 // Return a list of files in dir with given extension
fazekasgy@37 186 // Code taken from hostext/PluginLoader.cpp
fazekasgy@37 187 vector<string>
fazekasgy@37 188 PyPlugScanner::listFiles(string dir, string extension)
fazekasgy@37 189 {
fazekasgy@37 190 vector<string> files;
fazekasgy@37 191
fazekasgy@37 192 #ifdef _WIN32
fazekasgy@37 193
fazekasgy@37 194 string expression = dir + "\\*." + extension;
fazekasgy@37 195 WIN32_FIND_DATA data;
fazekasgy@37 196 HANDLE fh = FindFirstFile(expression.c_str(), &data);
fazekasgy@37 197 if (fh == INVALID_HANDLE_VALUE) return files;
fazekasgy@37 198
fazekasgy@37 199 bool ok = true;
fazekasgy@37 200 while (ok) {
fazekasgy@37 201 files.push_back(data.cFileName);
fazekasgy@37 202 ok = FindNextFile(fh, &data);
fazekasgy@37 203 }
fazekasgy@37 204
fazekasgy@37 205 FindClose(fh);
fazekasgy@37 206
fazekasgy@37 207 #else
fazekasgy@37 208
fazekasgy@37 209 size_t extlen = extension.length();
fazekasgy@37 210 DIR *d = opendir(dir.c_str());
fazekasgy@37 211 if (!d) return files;
fazekasgy@37 212
fazekasgy@37 213 struct dirent *e = 0;
fazekasgy@37 214 while ((e = readdir(d))) {
fazekasgy@37 215
fazekasgy@37 216 if (!e->d_name) continue;
fazekasgy@37 217
fazekasgy@37 218 size_t len = strlen(e->d_name);
fazekasgy@37 219 if (len < extlen + 2 ||
fazekasgy@37 220 e->d_name + len - extlen - 1 != "." + extension) {
fazekasgy@37 221 continue;
fazekasgy@37 222 }
fazekasgy@37 223 //cerr << "pyscripts: " << e->d_name << endl;
fazekasgy@37 224 files.push_back(e->d_name);
fazekasgy@37 225 }
fazekasgy@37 226
fazekasgy@37 227 closedir(d);
fazekasgy@37 228 #endif
fazekasgy@37 229
fazekasgy@37 230 return files;
fazekasgy@37 231 }
fazekasgy@37 232
fazekasgy@37 233
fazekasgy@37 234 //!!! It would probably be better to actually call
fazekasgy@37 235 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
fazekasgy@37 236 // needs to link against vamp-hostsdk, but that's probably acceptable
fazekasgy@37 237 // as it is sort of a host as well.
fazekasgy@37 238
fazekasgy@37 239 // std::vector<std::string>
fazekasgy@37 240 // PyPlugScanner::getAllValidPath()
fazekasgy@37 241 // {
fazekasgy@37 242 // Vamp::PluginHostAdapter host_adapter( ??? );
fazekasgy@37 243 // return host_adapter.getPluginPath();
fazekasgy@37 244 // }
fazekasgy@37 245
fazekasgy@37 246 // tried to implement it, but found a bit confusing how to
fazekasgy@37 247 // instantiate the host adapter here...
fazekasgy@37 248
fazekasgy@37 249
fazekasgy@37 250 //Return correct plugin directories as per platform
fazekasgy@37 251 //Code taken from vamp-sdk/PluginHostAdapter.cpp
fazekasgy@37 252 std::vector<std::string>
fazekasgy@37 253 PyPlugScanner::getAllValidPath()
fazekasgy@37 254 {
fazekasgy@37 255
fazekasgy@37 256 std::vector<std::string> path;
fazekasgy@37 257 std::string envPath;
fazekasgy@37 258
fazekasgy@37 259 char *cpath = getenv("VAMP_PATH");
fazekasgy@37 260 if (cpath) envPath = cpath;
fazekasgy@37 261
fazekasgy@37 262 #ifdef _WIN32
fazekasgy@37 263 #define PATH_SEPARATOR ';'
fazekasgy@37 264 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
fazekasgy@37 265 #else
fazekasgy@37 266 #define PATH_SEPARATOR ':'
fazekasgy@37 267 #ifdef __APPLE__
fazekasgy@37 268 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
fazekasgy@37 269 #else
fazekasgy@37 270 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
fazekasgy@37 271 #endif
fazekasgy@37 272 #endif
fazekasgy@37 273
fazekasgy@37 274 if (envPath == "") {
fazekasgy@37 275 envPath = DEFAULT_VAMP_PATH;
fazekasgy@37 276 char *chome = getenv("HOME");
fazekasgy@37 277 if (chome) {
fazekasgy@37 278 std::string home(chome);
fazekasgy@37 279 std::string::size_type f;
fazekasgy@37 280 while ((f = envPath.find("$HOME")) != std::string::npos &&
fazekasgy@37 281 f < envPath.length()) {
fazekasgy@37 282 envPath.replace(f, 5, home);
fazekasgy@37 283 }
fazekasgy@37 284 }
fazekasgy@37 285 #ifdef _WIN32
fazekasgy@37 286 char *cpfiles = getenv("ProgramFiles");
fazekasgy@37 287 if (!cpfiles) cpfiles = "C:\\Program Files";
fazekasgy@37 288 std::string pfiles(cpfiles);
fazekasgy@37 289 std::string::size_type f;
fazekasgy@37 290 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
fazekasgy@37 291 f < envPath.length()) {
fazekasgy@37 292 envPath.replace(f, 14, pfiles);
fazekasgy@37 293 }
fazekasgy@37 294 #endif
fazekasgy@37 295 }
fazekasgy@37 296
fazekasgy@37 297 std::string::size_type index = 0, newindex = 0;
fazekasgy@37 298
fazekasgy@37 299 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
fazekasgy@37 300 path.push_back(envPath.substr(index, newindex - index));
fazekasgy@37 301 index = newindex + 1;
fazekasgy@37 302 }
fazekasgy@37 303
fazekasgy@37 304 path.push_back(envPath.substr(index));
fazekasgy@37 305
fazekasgy@37 306 //can add an extra path for vampy plugins
fazekasgy@37 307 char* extraPath = getenv("VAMPY_EXTPATH");
fazekasgy@37 308 if (extraPath) {
fazekasgy@37 309 string vampyPath(extraPath);
fazekasgy@37 310 cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
fazekasgy@37 311 path.push_back(vampyPath);
fazekasgy@37 312 }
fazekasgy@37 313
fazekasgy@37 314 return path;
fazekasgy@37 315 }