Mercurial > hg > vampy
view PyPlugScanner.cpp @ 120:a38d318c85a9 tip
MSVC fixes
author | Chris Cannam |
---|---|
date | Wed, 18 Dec 2019 16:51:20 +0000 |
parents | a9e918adfff0 |
children |
line wrap: on
line source
/* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ /* * 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 "PyPlugScanner.h" #include "PyExtensionManager.h" #include "Debug.h" #include <algorithm> #include <cstdlib> //#include "vamp-hostsdk/PluginHostAdapter.h" #ifdef _WIN32 // We should be using the unicode apis, but we're not (yet) #undef UNICODE #undef _UNICODE #define _MBCS 1 #include <windows.h> #include <tchar.h> #define pathsep ("\\") #else #include <dirent.h> #include <dlfcn.h> #define pathsep ("/") #endif #define joinPath(a,b) ( (a)+pathsep+(b) ) using std::string; using std::vector; using std::cerr; using std::endl; using std::find; PyPlugScanner::PyPlugScanner() { } PyPlugScanner *PyPlugScanner::m_instance = NULL; bool PyPlugScanner::m_hasInstance = false; PyPlugScanner* PyPlugScanner::getInstance() { if (!m_hasInstance) { m_instance = new PyPlugScanner(); m_hasInstance = true; } return m_instance; } void PyPlugScanner::setPath(vector<string> path) { m_path=path; } // We assume that each script on the path has one valid class vector<string> PyPlugScanner::getPyPlugs() { //for_each m_path listFiles then return vector<pyPlugs> //key format: FullPathString/FileName.py:ClassName bool getCompiled = true; char* getPyc = getenv("VAMPY_COMPILED"); if (getPyc) { string value(getPyc); cerr << "VAMPY_COMPILED=" << value << endl; getCompiled = value.compare("1")?false:true; } vector<string> pyPlugs; string pluginKey; PyObject *pyClass; for (size_t i = 0; i < m_path.size(); ++i) { vector<string> files = listFiles(m_path[i],"py"); /// recognise byte compiled plugins if (getCompiled) { vector<string> pyc_files = listFiles(m_path[i],"pyc"); vector<string> pyo_files = listFiles(m_path[i],"pyo"); mergeFileLists(pyc_files,pyo_files,".pyo"); mergeFileLists(pyo_files,files,".py"); } for (vector<string>::iterator fi = files.begin(); fi != files.end(); ++fi) { string script = *fi; if (!script.empty()) { string classname=script.substr(0,script.rfind('.')); pluginKey=joinPath(m_path[i],script)+":"+classname; pyClass = getScriptClass(m_path[i],classname); if (pyClass == NULL) cerr << "Warning: Syntax error or other problem in scanning VamPy plugin: " << classname << ". Avoiding plugin." << endl; else { pyPlugs.push_back(pluginKey); m_pyClasses.push_back(pyClass); } } } } return pyPlugs; } /// insert python byte code names (.pyc) if a .py file can not be found /// The interpreter automatically generates byte code files and executes /// them if they exist. Therefore, we prefer .py files, but we allow /// (relatively) closed source distributions by recognising .pyc files. void PyPlugScanner::mergeFileLists(vector<string> &src, vector<string> &tg, string target_ext) { for (vector<string>::iterator srcit = src.begin(); srcit != src.end(); ++srcit) { // cerr << *srcit; string src_name = *srcit; string tg_name = src_name.substr(0,src_name.rfind('.')) + target_ext; vector<string>::iterator tgit = find (tg.begin(), tg.end(), tg_name); if (tgit == tg.end()) tg.push_back(src_name); } } //For now return one class object found in each script vector<PyObject*> PyPlugScanner::getPyClasses() { return m_pyClasses; } //Validate //This should not be called more than once! PyObject* PyPlugScanner::getScriptClass(string path, string classname) { //Add plugin path to active Python Path string pyCmd = "import sys\n" "sys.path.append('" + path + "')\n" "sys.argv = ['" + classname + "']\n"; PyRun_SimpleString(pyCmd.c_str()); //Assign an object to the source code PyObject *pySource = PyString_FromString(classname.c_str()); //Import it as a module into the py interpreter PyObject *pyModule = PyImport_Import(pySource); PyObject* pyError = PyErr_Occurred(); if (pyError) { cerr << "ERROR: error importing source: " << classname << endl; PyErr_Print(); Py_DECREF(pySource); Py_CLEAR(pyModule); // safer if pyModule==NULL return NULL; } Py_DECREF(pySource); //Read the dictionary object holding the namespace of the module (borrowed reference) PyObject *pyDict = PyModule_GetDict(pyModule); Py_DECREF(pyModule); //Get the PluginClass from the module (borrowed reference) PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str()); if (pyClass == Py_None) { DSTREAM << "Vampy: class name " << classname << " is None in module; assuming it was scrubbed " << "following an earlier load failure" << endl; return NULL; } // Check if class is present and a callable method is implemented if (!pyClass || !PyCallable_Check(pyClass)) { cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl << "Hint: plugin source filename and plugin class name must be the same." << endl; PyErr_Print(); return NULL; } bool acceptable = true; // Check that the module doesn't have any name collisions with // our own symbols int i = 0; while (PyExtensionManager::m_exposedNames[i]) { const char* name = PyExtensionManager::m_exposedNames[i]; i++; PyObject *item = PyDict_GetItemString(pyDict, name); if (!item) continue; if (item == Py_None) { DSTREAM << "Vampy: name " << name << " is None " << "in module " << classname << "; assuming it was cleared on unload" << endl; continue; } PyTypeObject *metatype = Py_TYPE(item); if (!metatype) { cerr << "ERROR: plugin " << classname << " reports null metatype for Vampy name \"" << name << "\" (incomplete due to load error?)" << endl; acceptable = false; break; } if (!strcmp(name, "frame2RealTime")) { if (metatype != &PyCFunction_Type) { cerr << "ERROR: plugin " << classname << " redefines Vampy function name \"" << name << "\" (metatype is \"" << metatype->tp_name << "\")" << endl; acceptable = false; break; } else { continue; } } if (metatype != &PyType_Type) { cerr << "ERROR: plugin " << classname << " uses Vampy reserved type name \"" << name << "\" for non-type (metatype is \"" << metatype->tp_name << "\")" << endl; acceptable = false; break; } PyTypeObject *type = (PyTypeObject *)item; if (type->tp_name == std::string("vampy.") + name) { DSTREAM << "Vampy: acceptable Vampy type name " << type->tp_name << " found in module" << endl; } else { cerr << "ERROR: plugin " << classname << " redefines Vampy type \"" << name << "\""; if (strcmp(type->tp_name, name)) { cerr << " (as \"" << type->tp_name << "\")"; } cerr << endl; acceptable = false; break; } } if (acceptable) { return pyClass; } else { PyObject *key = PyString_FromString(classname.c_str()); PyDict_SetItem(pyDict, key, Py_None); Py_DECREF(key); return NULL; } } // Return a list of files in dir with given extension // Code taken from hostext/PluginLoader.cpp vector<string> PyPlugScanner::listFiles(string dir, string extension) { vector<string> files; #ifdef _WIN32 string expression = dir + "\\*." + extension; WIN32_FIND_DATA data; HANDLE fh = FindFirstFile(expression.c_str(), &data); if (fh == INVALID_HANDLE_VALUE) return files; bool ok = true; while (ok) { files.push_back(data.cFileName); ok = FindNextFile(fh, &data); } FindClose(fh); #else size_t extlen = extension.length(); DIR *d = opendir(dir.c_str()); if (!d) return files; struct dirent *e = 0; while ((e = readdir(d))) { size_t len = strlen(e->d_name); if (len < extlen + 2 || e->d_name + len - extlen - 1 != "." + extension) { continue; } files.push_back(e->d_name); } closedir(d); #endif return files; } //!!! It would probably be better to actually call // PluginHostAdapter::getPluginPath. That would mean this "plugin" // needs to link against vamp-hostsdk, but that's probably acceptable // as it is sort of a host as well. // std::vector<std::string> // PyPlugScanner::getAllValidPath() // { // Vamp::PluginHostAdapter host_adapter( ??? ); // return host_adapter.getPluginPath(); // } // tried to implement it, but found a bit confusing how to // instantiate the host adapter here... //Return correct plugin directories as per platform //Code taken from vamp-sdk/PluginHostAdapter.cpp std::vector<std::string> PyPlugScanner::getAllValidPath() { std::vector<std::string> path; std::string envPath; bool nonNative32 = false; #ifdef _WIN32 BOOL (WINAPI *fnIsWow64Process)(HANDLE, PBOOL) = (BOOL (WINAPI *)(HANDLE, PBOOL)) GetProcAddress (GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); if (fnIsWow64Process) { BOOL wow64 = FALSE; if (fnIsWow64Process(GetCurrentProcess(), &wow64) && wow64) { nonNative32 = true; } } #endif char *cpath; if (nonNative32) { cpath = getenv("VAMP_PATH_32"); } else { cpath = getenv("VAMP_PATH"); } if (cpath) envPath = cpath; #ifdef _WIN32 #define PATH_SEPARATOR ';' #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" #else #define PATH_SEPARATOR ':' #ifdef __APPLE__ #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" #else #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" #endif #endif if (envPath == "") { envPath = DEFAULT_VAMP_PATH; char *chome = getenv("HOME"); if (chome) { std::string home(chome); std::string::size_type f; while ((f = envPath.find("$HOME")) != std::string::npos && f < envPath.length()) { envPath.replace(f, 5, home); } } #ifdef _WIN32 const char *cpfiles = getenv("ProgramFiles"); if (!cpfiles) cpfiles = "C:\\Program Files"; std::string pfiles(cpfiles); std::string::size_type f; while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && f < envPath.length()) { envPath.replace(f, 14, pfiles); } #endif } std::string::size_type index = 0, newindex = 0; while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { path.push_back(envPath.substr(index, newindex - index)); index = newindex + 1; } path.push_back(envPath.substr(index)); //can add an extra path for vampy plugins char* extraPath = getenv("VAMPY_EXTPATH"); if (extraPath) { string vampyPath(extraPath); cerr << "VAMPY_EXTPATH=" << vampyPath << endl; path.push_back(vampyPath); } return path; }