annotate vampy-main.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 c1e4f706ca9a
children f0592002c61d
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 #include <Python.h>
fazekasgy@37 13
fazekasgy@37 14 #ifdef HAVE_NUMPY
fazekasgy@51 15
fazekasgy@51 16 // define a unique API pointer
fazekasgy@51 17 #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API
fazekasgy@37 18 #include "numpy/arrayobject.h"
fazekasgy@51 19
fazekasgy@51 20 // prevent building with very old versions of numpy
fazekasgy@51 21 #ifndef NPY_VERSION
fazekasgy@51 22 #undef HAVE_NUMPY
fazekasgy@51 23 #endif
fazekasgy@51 24
fazekasgy@51 25 #endif
fazekasgy@51 26
fazekasgy@51 27 // this is not part of the API, but we will require it for a bug workaround
fazekasgy@51 28 // define this symbol if you use another version of numpy in the makefile
fazekasgy@51 29 // Vampy will not attempt to load a lower version than specified
fazekasgy@51 30 #ifdef HAVE_NUMPY
fazekasgy@51 31 #ifndef NUMPY_SHORTVERSION
fazekasgy@51 32 #define NUMPY_SHORTVERSION 1.1
fazekasgy@51 33 #endif
fazekasgy@37 34 #endif
fazekasgy@37 35
fazekasgy@37 36 #include "vamp/vamp.h"
fazekasgy@37 37 #include "vamp-sdk/PluginAdapter.h"
fazekasgy@37 38 #include "PyPlugScanner.h"
fazekasgy@37 39 #include "PyPlugin.h"
fazekasgy@37 40 #include "PyExtensionModule.h"
fazekasgy@37 41 #include "PyExtensionManager.h"
fazekasgy@37 42
fazekasgy@37 43
fazekasgy@37 44 #ifdef _WIN32
fazekasgy@37 45 #define pathsep ('\\')
fazekasgy@37 46 #include <windows.h>
fazekasgy@37 47 #include <tchar.h>
fazekasgy@37 48 #else
fazekasgy@37 49 #define pathsep ('/')
fazekasgy@37 50 #include <dirent.h>
fazekasgy@37 51 #include <dlfcn.h>
fazekasgy@37 52 #endif
fazekasgy@37 53
fazekasgy@37 54 using std::cerr;
fazekasgy@37 55 using std::endl;
fazekasgy@37 56 using std::string;
fazekasgy@37 57 using std::vector;
fazekasgy@37 58
fazekasgy@37 59 static int adinstcount;
fazekasgy@37 60 static int totinstcount;
fazekasgy@51 61 static bool numpyInstalled = false;
fazekasgy@51 62 static bool arrayApiInitialised = false;
fazekasgy@37 63
fazekasgy@37 64 class PyPluginAdapter : public Vamp::PluginAdapterBase
fazekasgy@37 65 {
fazekasgy@37 66 public:
fazekasgy@37 67 PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) :
fazekasgy@37 68 PluginAdapterBase(),
fazekasgy@37 69 m_plug(pyPlugId),
fazekasgy@37 70 m_pyClass(pyClass),
fazekasgy@37 71 m_failed(false)
fazekasgy@37 72 {
fazekasgy@37 73 cerr << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl;
fazekasgy@37 74 adinstcount++;
fazekasgy@37 75 }
fazekasgy@37 76
fazekasgy@37 77 ~PyPluginAdapter()
fazekasgy@37 78 {
fazekasgy@37 79 }
fazekasgy@37 80
fazekasgy@37 81 bool failed() { return m_failed; }
fazekasgy@37 82 std::string getPlugKey() { return m_plug; }
fazekasgy@37 83
fazekasgy@37 84 protected:
fazekasgy@37 85 Vamp::Plugin *createPlugin(float inputSampleRate)
fazekasgy@37 86 {
fazekasgy@37 87 try {
fazekasgy@51 88 PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount, numpyInstalled);
fazekasgy@37 89 return plugin;
fazekasgy@37 90 } catch (...) {
fazekasgy@37 91 cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl;
fazekasgy@37 92 // any plugin with syntax errors will fail to construct
fazekasgy@37 93 m_failed = true;
fazekasgy@37 94 return 0;
fazekasgy@37 95 }
fazekasgy@37 96 }
fazekasgy@37 97
fazekasgy@37 98 std::string m_plug;
fazekasgy@37 99 PyObject *m_pyClass;
fazekasgy@37 100 bool m_failed;
fazekasgy@37 101 };
fazekasgy@37 102
fazekasgy@51 103
fazekasgy@37 104 static void array_API_initialiser()
fazekasgy@37 105 {
fazekasgy@51 106 if (arrayApiInitialised) return;
fazekasgy@51 107
fazekasgy@51 108 /* Numpy 1.3 build note: there seems to be a bug
fazekasgy@51 109 in this version (at least on OS/X) which will cause memory
fazekasgy@51 110 access error in the array API import function if an earlier runtime
fazekasgy@51 111 version of Numpy is used when loading the library.
fazekasgy@51 112 (below is a horrible workaround)
fazekasgy@51 113 */
fazekasgy@51 114
fazekasgy@37 115 #ifdef HAVE_NUMPY
fazekasgy@51 116
fazekasgy@51 117 string ver;
fazekasgy@51 118 float numpyVersion;
fazekasgy@51 119
fazekasgy@51 120 /// attmept to test numpy version before importing the array API
fazekasgy@51 121 cerr << "Numpy build information: ABI level: " << NPY_VERSION
fazekasgy@51 122 << " Numpy version: " << NUMPY_SHORTVERSION << endl;
fazekasgy@51 123
fazekasgy@51 124 PyObject *pyModule, *pyDict, *pyVer;
fazekasgy@51 125
fazekasgy@51 126 pyModule = PyImport_ImportModule("numpy"); //numpy.core.multiarray
fazekasgy@51 127 if (!pyModule) {
fazekasgy@51 128 cerr << "Vampy was compiled with Numpy support but Numpy does not seem to be installed." << endl;
fazekasgy@51 129 #ifdef __APPLE__
fazekasgy@51 130 cerr << "Hint: Check if Numpy is installed for the particular setup of Python used by Vampy (given by Python exec prefix)." << endl;
fazekasgy@51 131 #endif
fazekasgy@51 132 goto numpyFailure;
fazekasgy@51 133 }
fazekasgy@51 134
fazekasgy@51 135 pyDict = PyModule_GetDict(pyModule); // borrowed ref
fazekasgy@51 136 if (!pyDict) {
fazekasgy@51 137 cerr << "Can not access Numpy module dictionary." << endl;
fazekasgy@51 138 goto numpyFailure;
fazekasgy@51 139 }
fazekasgy@51 140
fazekasgy@51 141 pyVer = PyDict_GetItemString(pyDict,"__version__"); //borrowed ref
fazekasgy@51 142 if (!pyVer) {
fazekasgy@51 143 cerr << "Can not access Numpy version information." << endl;
fazekasgy@51 144 goto numpyFailure;
fazekasgy@51 145 }
fazekasgy@51 146
fazekasgy@51 147 ver = PyString_AsString(pyVer);
fazekasgy@51 148 ver = ver.substr(0,ver.rfind("."));
fazekasgy@51 149 if(EOF == sscanf(ver.c_str(), "%f", &numpyVersion))
fazekasgy@51 150 {
fazekasgy@51 151 cerr << "Could not parse Numpy version information." << endl;
fazekasgy@51 152 goto numpyFailure;
fazekasgy@51 153 }
fazekasgy@51 154
fazekasgy@51 155 cerr << "Numpy runtime version: " << numpyVersion << endl;
fazekasgy@51 156 if (numpyVersion < (float) NUMPY_SHORTVERSION) {
fazekasgy@51 157 cerr << "Incompatible Numpy version found: " << numpyVersion << endl;
fazekasgy@51 158 goto numpyFailure;
fazekasgy@51 159 }
fazekasgy@51 160
fazekasgy@51 161 Py_DECREF(pyModule);
fazekasgy@51 162
fazekasgy@51 163 // At least we catch import errors, but if binary compatibility
fazekasgy@51 164 // has changed without notice, this would still fail.
fazekasgy@51 165 // However, we should never get to this point now anyway.
fazekasgy@37 166 import_array();
fazekasgy@51 167 if (PyErr_Occurred()) {
fazekasgy@51 168 cerr << "Import error while loading the Numpy Array API." << endl;
fazekasgy@51 169 PyErr_Print(); PyErr_Clear();
fazekasgy@51 170 goto numpyFailure;
fazekasgy@51 171 }
fazekasgy@51 172 else {
fazekasgy@51 173
fazekasgy@51 174 #ifdef _DEBUG
fazekasgy@51 175 if (NPY_VERSION != PyArray_GetNDArrayCVersion()) {
fazekasgy@51 176 // the Import function does this check already.
fazekasgy@51 177 cerr << "Warning: Numpy version mismatch. (Build version: "
fazekasgy@51 178 << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl;
fazekasgy@51 179 goto numpyFailure;
fazekasgy@51 180 }
fazekasgy@37 181 #endif
fazekasgy@51 182
fazekasgy@51 183 numpyInstalled = true;
fazekasgy@51 184 arrayApiInitialised = true;
fazekasgy@51 185 return;
fazekasgy@51 186 }
fazekasgy@51 187
fazekasgy@51 188
fazekasgy@51 189 numpyFailure:
fazekasgy@51 190 cerr << "Please make sure you have Numpy " << NUMPY_SHORTVERSION << " or greater installed." << endl;
fazekasgy@51 191 cerr << "Vampy: Numpy support disabled." << endl;
fazekasgy@51 192 numpyInstalled = false;
fazekasgy@51 193 arrayApiInitialised = true;
fazekasgy@51 194 if (pyModule) Py_XDECREF(pyModule);
fazekasgy@51 195 return;
fazekasgy@51 196
fazekasgy@51 197 /*HAVE_NUMPY*/
fazekasgy@51 198 #endif
fazekasgy@51 199
fazekasgy@51 200 numpyInstalled = false;
fazekasgy@51 201 arrayApiInitialised = true;
fazekasgy@51 202 return;
fazekasgy@37 203 }
fazekasgy@37 204
fazekasgy@37 205
fazekasgy@37 206 static std::vector<PyPluginAdapter *> adapters;
fazekasgy@37 207 static bool haveScannedPlugins = false;
fazekasgy@37 208
fazekasgy@37 209 static bool tryPreload(string name)
fazekasgy@37 210 {
cannam@53 211 // cerr << "tryPreload: " << name << endl;
fazekasgy@37 212 #ifdef _WIN32
fazekasgy@37 213 void *lib = LoadLibrary(name.c_str());
fazekasgy@37 214 if (!lib) {
fazekasgy@37 215 return false;
fazekasgy@37 216 }
fazekasgy@37 217 #else
fazekasgy@37 218 void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
fazekasgy@37 219 if (!lib) {
cannam@53 220 perror("dlopen");
fazekasgy@37 221 return false;
fazekasgy@37 222 }
fazekasgy@37 223 #endif
cannam@53 224 cerr << "Preloaded Python from " << name << endl;
fazekasgy@37 225 return true;
fazekasgy@37 226 }
fazekasgy@37 227
fazekasgy@37 228 static bool preloadPython()
fazekasgy@37 229 {
fazekasgy@37 230 #ifdef _WIN32
fazekasgy@37 231 // this doesn't seem to be necessary at all on Windows
fazekasgy@37 232 return true;
fazekasgy@37 233 #endif
fazekasgy@37 234
fazekasgy@37 235 string pyver = Py_GetVersion();
fazekasgy@37 236 int dots = 2;
fazekasgy@37 237 string shortver;
fazekasgy@37 238 for (size_t i = 0; i < pyver.length(); ++i) {
fazekasgy@37 239 if (pyver[i] == '.') {
fazekasgy@37 240 if (--dots == 0) {
fazekasgy@37 241 shortver = pyver.substr(0, i);
fazekasgy@37 242 break;
fazekasgy@37 243 }
fazekasgy@37 244 }
fazekasgy@37 245 }
fazekasgy@37 246 cerr << "Short version: " << shortver << endl;
fazekasgy@37 247 // this is useful to find out where the loaded library might be loaded from
fazekasgy@37 248 cerr << "Python exec prefix: " << Py_GetExecPrefix() << endl;
fazekasgy@37 249
fazekasgy@37 250 vector<string> pfxs;
cannam@53 251 pfxs.push_back(string(Py_GetExecPrefix()) + "/");
cannam@53 252 pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/");
fazekasgy@37 253 pfxs.push_back("");
fazekasgy@37 254 pfxs.push_back("/usr/lib/");
fazekasgy@37 255 pfxs.push_back("/usr/local/lib/");
fazekasgy@37 256 char buffer[5];
fazekasgy@37 257
fazekasgy@37 258 // hahaha! grossness is like a brother to us
fazekasgy@37 259 #ifdef __APPLE__
fazekasgy@37 260 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
cannam@53 261 // cerr << "prefix: " << pfxs[pfxidx] << endl;
cannam@53 262 if (tryPreload(pfxs[pfxidx] + string("Python"))) return true;
fazekasgy@37 263 for (int minor = 8; minor >= 0; --minor) {
fazekasgy@37 264 sprintf(buffer, "%d", minor);
fazekasgy@37 265 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true;
fazekasgy@37 266 }
fazekasgy@37 267 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true;
fazekasgy@37 268 if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true;
fazekasgy@37 269 }
fazekasgy@37 270 #else
fazekasgy@37 271 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
fazekasgy@37 272 for (int minor = 8; minor >= 0; --minor) {
fazekasgy@37 273 sprintf(buffer, "%d", minor);
fazekasgy@37 274 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true;
fazekasgy@37 275 }
fazekasgy@37 276 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true;
fazekasgy@37 277 if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true;
fazekasgy@37 278 }
fazekasgy@37 279 #endif
fazekasgy@37 280
fazekasgy@37 281 return false;
fazekasgy@37 282 }
fazekasgy@37 283
fazekasgy@37 284
fazekasgy@37 285 static PyExtensionManager pyExtensionManager;
fazekasgy@37 286
fazekasgy@37 287 const VampPluginDescriptor
fazekasgy@37 288 *vampGetPluginDescriptor(unsigned int version,unsigned int index)
fazekasgy@37 289 {
fazekasgy@37 290 if (version < 1) return 0;
fazekasgy@37 291
fazekasgy@37 292 int isPythonInitialized = Py_IsInitialized();
fazekasgy@37 293 cerr << "# isPythonInitialized: " << isPythonInitialized << endl;
fazekasgy@37 294 cerr << "# haveScannedPlugins: " << haveScannedPlugins << endl;
fazekasgy@37 295
fazekasgy@37 296 if (!haveScannedPlugins) {
fazekasgy@37 297
fazekasgy@37 298 if (!isPythonInitialized){
fazekasgy@37 299
fazekasgy@37 300 if (!preloadPython())
fazekasgy@37 301 cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl;
fazekasgy@37 302 if (PyImport_AppendInittab("vampy",initvampy) != 0)
fazekasgy@37 303 cerr << "Warning: Extension module could not be added to module inittab." << endl;
fazekasgy@37 304 Py_Initialize();
fazekasgy@51 305 array_API_initialiser();
fazekasgy@37 306 initvampy();
fazekasgy@37 307 #ifdef _DEBUG
fazekasgy@37 308 cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl;
fazekasgy@37 309 #endif
fazekasgy@37 310 }
fazekasgy@37 311
fazekasgy@37 312 vector<string> pyPlugs;
fazekasgy@37 313 vector<string> pyPath;
fazekasgy@37 314 vector<PyObject *> pyClasses;
fazekasgy@37 315 static PyPlugScanner *scanner;
fazekasgy@37 316
fazekasgy@37 317 //Scanning Plugins
fazekasgy@37 318 cerr << "Scanning Vampy Plugins" << endl;
fazekasgy@37 319 scanner = PyPlugScanner::getInstance();
fazekasgy@37 320
fazekasgy@37 321 // added env. varable support VAMPY_EXTPATH
fazekasgy@37 322 pyPath=scanner->getAllValidPath();
fazekasgy@37 323 scanner->setPath(pyPath);
fazekasgy@37 324
fazekasgy@37 325 // added env. variable support:
fazekasgy@37 326 // VAMPY_COMPILED=1 to recognise .pyc files (default is 1)
fazekasgy@37 327 pyPlugs = scanner->getPyPlugs();
fazekasgy@37 328
fazekasgy@37 329 cerr << "Found " << pyPlugs.size() << " Scripts." << endl;
fazekasgy@37 330 //TODO: should this support multiple classes per script (?)
fazekasgy@37 331 pyClasses = scanner->getPyClasses();
fazekasgy@37 332 cerr << "Found " << pyClasses.size() << " Classes." << endl;
fazekasgy@37 333
fazekasgy@37 334 for (size_t i = 0; i < pyPlugs.size(); ++i) {
fazekasgy@37 335 adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i]));
fazekasgy@37 336 }
fazekasgy@37 337 pyExtensionManager.setPlugModuleNames(pyPlugs);
fazekasgy@37 338 pyExtensionManager.initExtension();
fazekasgy@37 339 array_API_initialiser();
fazekasgy@37 340 haveScannedPlugins=true;
fazekasgy@37 341 }
fazekasgy@37 342
fazekasgy@37 343 #ifdef _DEBUG
fazekasgy@37 344 cerr << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl;
fazekasgy@37 345 #endif
fazekasgy@37 346
fazekasgy@37 347 if (index<adapters.size()) {
fazekasgy@37 348
fazekasgy@37 349 const VampPluginDescriptor *tmp = adapters[index]->getDescriptor();
fazekasgy@37 350
fazekasgy@37 351 if (adapters[index]->failed()) {
fazekasgy@37 352 cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'"
fazekasgy@37 353 << adapters[index]->getPlugKey() << "'\n"
fazekasgy@37 354 << "The plugin has failed to construct. Hint: Check __init__() function." << endl;
fazekasgy@37 355 pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey());
fazekasgy@37 356 delete adapters[index];
fazekasgy@37 357 adapters.erase(adapters.begin()+index);
fazekasgy@37 358 return 0;
fazekasgy@37 359 }
fazekasgy@37 360
fazekasgy@37 361 return tmp;
fazekasgy@37 362
fazekasgy@37 363 } else return 0;
fazekasgy@37 364 }
fazekasgy@37 365
fazekasgy@37 366
fazekasgy@37 367
fazekasgy@37 368
fazekasgy@37 369
fazekasgy@37 370
fazekasgy@37 371
fazekasgy@37 372