view 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
line wrap: on
line source
/*

 * Vampy : This plugin is a wrapper around the Vamp plugin API.
 * It allows for writing Vamp plugins in Python.

 * Centre for Digital Music, Queen Mary University of London.
 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources 
 * for licence information.)

*/

#include <Python.h>

#ifdef HAVE_NUMPY

// define a unique API pointer 
#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API 
#include "numpy/arrayobject.h"

// prevent building with very old versions of numpy
#ifndef NPY_VERSION 
#undef HAVE_NUMPY
#endif

#endif

// this is not part of the API, but we will require it for a bug workaround
// define this symbol if you use another version of numpy in the makefile
// Vampy will not attempt to load a lower version than specified
#ifdef HAVE_NUMPY
#ifndef NUMPY_SHORTVERSION
#define NUMPY_SHORTVERSION 1.1 
#endif
#endif

#include "vamp/vamp.h"
#include "vamp-sdk/PluginAdapter.h"
#include "PyPlugScanner.h"
#include "PyPlugin.h"
#include "PyExtensionModule.h"
#include "PyExtensionManager.h"


#ifdef _WIN32
#define pathsep ('\\')
#include <windows.h>
#include <tchar.h>
#else 
#define pathsep ('/')
#include <dirent.h>
#include <dlfcn.h>
#endif

using std::cerr;
using std::endl;
using std::string;
using std::vector;

static int adinstcount;
static int totinstcount;
static bool numpyInstalled = false;
static bool arrayApiInitialised = false;

class PyPluginAdapter : public Vamp::PluginAdapterBase
{
public: 
    PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) :
        PluginAdapterBase(),
        m_plug(pyPlugId),		
        m_pyClass(pyClass),
		m_failed(false)
    { 
        cerr << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl; 
        adinstcount++;
    }
    
    ~PyPluginAdapter() 
    {
    }

	bool failed() { return m_failed; }
	std::string getPlugKey() { return m_plug; }

protected:
    Vamp::Plugin *createPlugin(float inputSampleRate)
    {
        try {
            PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount, numpyInstalled);
            return plugin;
        } catch (...) {
            cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl;
			// any plugin with syntax errors will fail to construct
			m_failed = true;
            return 0;
        }
    }
    
    std::string m_plug;
    PyObject *m_pyClass;
	bool m_failed;  
};


static void array_API_initialiser()
{
	if (arrayApiInitialised) return; 

/* Numpy 1.3 build note: there seems to be a bug 
in this version (at least on OS/X) which will cause memory 
access error in the array API import function if an earlier runtime 
version of Numpy is used when loading the library.
(below is a horrible workaround)
*/

#ifdef HAVE_NUMPY

	string ver;
	float numpyVersion;

	/// attmept to test numpy version before importing the array API
	cerr << "Numpy build information: ABI level: " << NPY_VERSION 
	<< " Numpy version: " << NUMPY_SHORTVERSION << endl;
	
	PyObject *pyModule, *pyDict, *pyVer;
	
	pyModule = PyImport_ImportModule("numpy"); //numpy.core.multiarray
	if (!pyModule) {
		cerr << "Vampy was compiled with Numpy support but Numpy does not seem to be installed." << endl;
#ifdef __APPLE__
		cerr << "Hint: Check if Numpy is installed for the particular setup of Python used by Vampy (given by Python exec prefix)." << endl;
#endif		
		goto numpyFailure;
	}

	pyDict = PyModule_GetDict(pyModule); // borrowed ref
	if (!pyDict) {
		cerr << "Can not access Numpy module dictionary." << endl;
		goto numpyFailure;
	}

	pyVer = PyDict_GetItemString(pyDict,"__version__"); //borrowed ref
	if (!pyVer) {
		cerr << "Can not access Numpy version information." << endl;
		goto numpyFailure;
	}

	ver = PyString_AsString(pyVer);
	ver = ver.substr(0,ver.rfind("."));
	if(EOF == sscanf(ver.c_str(), "%f", &numpyVersion))
	{
		cerr << "Could not parse Numpy version information." << endl;
		goto numpyFailure;
	}

	cerr << "Numpy runtime version: " << numpyVersion << endl;
	if (numpyVersion < (float) NUMPY_SHORTVERSION) {
		cerr << "Incompatible Numpy version found: " << numpyVersion << endl;
		goto numpyFailure;
	}

	Py_DECREF(pyModule);

	// At least we catch import errors, but if binary compatibility
	// has changed without notice, this would still fail.
	// However, we should never get to this point now anyway.
	import_array();
	if (PyErr_Occurred()) { 
		cerr << "Import error while loading the Numpy Array API." << endl;
		PyErr_Print(); PyErr_Clear(); 
		goto numpyFailure;
	}
	else {

#ifdef _DEBUG		
		if (NPY_VERSION != PyArray_GetNDArrayCVersion()) {  
			// the Import function does this check already.
			cerr << "Warning: Numpy version mismatch. (Build version: " 
				<< NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl;
			goto numpyFailure; 
		}
#endif

		numpyInstalled = true;
		arrayApiInitialised = true;
		return;
  	}


numpyFailure: 
	cerr << "Please make sure you have Numpy " << NUMPY_SHORTVERSION << " or greater installed." << endl;
	cerr << "Vampy: Numpy support disabled." << endl;
	numpyInstalled = false;
	arrayApiInitialised = true;
	if (pyModule) Py_XDECREF(pyModule);
	return;

/*HAVE_NUMPY*/
#endif 

    numpyInstalled = false;
	arrayApiInitialised = true;
	return;
}


static std::vector<PyPluginAdapter *> adapters;
static bool haveScannedPlugins = false;

static bool tryPreload(string name)
{
//    cerr << "tryPreload: " << name << endl;
#ifdef _WIN32
    void *lib = LoadLibrary(name.c_str());
    if (!lib) {
        return false;
    }
#else
    void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
    if (!lib) {
        perror("dlopen");
        return false;
    }
#endif
    cerr << "Preloaded Python from " << name << endl;
    return true;
}

static bool preloadPython()
{
#ifdef _WIN32
    // this doesn't seem to be necessary at all on Windows
    return true;
#endif

    string pyver = Py_GetVersion();
    int dots = 2;
    string shortver;
    for (size_t i = 0; i < pyver.length(); ++i) {
        if (pyver[i] == '.') {
            if (--dots == 0) {
                shortver = pyver.substr(0, i);
                break;
            }
        }
    }
    cerr << "Short version: " << shortver << endl;
	// this is useful to find out where the loaded library might be loaded from
	cerr << "Python exec prefix: " << Py_GetExecPrefix() << endl;

    vector<string> pfxs;
    pfxs.push_back(string(Py_GetExecPrefix()) + "/");
    pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/");
    pfxs.push_back("");
    pfxs.push_back("/usr/lib/");
    pfxs.push_back("/usr/local/lib/");
    char buffer[5];

    // hahaha! grossness is like a brother to us
#ifdef __APPLE__
    for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
//        cerr << "prefix: " << pfxs[pfxidx] << endl;
	if (tryPreload(pfxs[pfxidx] + string("Python"))) return true;
        for (int minor = 8; minor >= 0; --minor) {
            sprintf(buffer, "%d", minor);
            if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true;
        }
        if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true;
        if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true;
    }
#else
    for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
        for (int minor = 8; minor >= 0; --minor) {
            sprintf(buffer, "%d", minor);
            if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true;
        }
        if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true;
        if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true;
    }
#endif
        
    return false;
}


static PyExtensionManager pyExtensionManager;

const VampPluginDescriptor 
*vampGetPluginDescriptor(unsigned int version,unsigned int index)
{	
    if (version < 1) return 0;

	int isPythonInitialized = Py_IsInitialized();
	cerr << "# isPythonInitialized: " << isPythonInitialized << endl;
	cerr << "# haveScannedPlugins: " << haveScannedPlugins << endl;

	if (!haveScannedPlugins) {

		if (!isPythonInitialized){

			if (!preloadPython())
				cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl;
			if (PyImport_AppendInittab("vampy",initvampy) != 0)
				cerr << "Warning: Extension module could not be added to module inittab." << endl;
			Py_Initialize();
			array_API_initialiser();
			initvampy();
#ifdef _DEBUG			
		    cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl;
#endif		
		}

		vector<string> pyPlugs;
		vector<string> pyPath;
		vector<PyObject *> pyClasses;
		static PyPlugScanner *scanner;
		
		//Scanning Plugins
		cerr << "Scanning Vampy Plugins" << endl;
		scanner = PyPlugScanner::getInstance();

		// added env. varable support VAMPY_EXTPATH
		pyPath=scanner->getAllValidPath();
		scanner->setPath(pyPath);
		
		// added env. variable support: 
		// VAMPY_COMPILED=1 to recognise .pyc files (default is 1)
		pyPlugs = scanner->getPyPlugs();

		cerr << "Found " << pyPlugs.size() << " Scripts." << endl;
		//TODO: should this support multiple classes per script (?)
		pyClasses = scanner->getPyClasses();
		cerr << "Found " << pyClasses.size() << " Classes." << endl;

		for (size_t i = 0; i < pyPlugs.size(); ++i) {
			adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i]));
		}
		pyExtensionManager.setPlugModuleNames(pyPlugs);
		pyExtensionManager.initExtension();
		array_API_initialiser();
		haveScannedPlugins=true;
	}

#ifdef _DEBUG
	cerr << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl;
#endif	

	if (index<adapters.size()) {

		const VampPluginDescriptor *tmp = adapters[index]->getDescriptor();

		if (adapters[index]->failed()) { 
			cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'" 
			<< adapters[index]->getPlugKey() << "'\n" 
			<< "The plugin has failed to construct. Hint: Check __init__() function." << endl;
			pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey());
			delete adapters[index];
			adapters.erase(adapters.begin()+index);
			return 0;
		}

		return tmp;

	} else return 0;
}