diff options
author | Michael Haberler <git@mah.priv.at> | 2011-07-21 00:14:12 +0200 |
---|---|---|
committer | Michael Haberler <git@mah.priv.at> | 2011-10-28 08:25:52 +0200 |
commit | 7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4 (patch) | |
tree | ec1a4b16877b7b42ffa7a85093020d2272f8abf3 | |
parent | 1b14351210a3a5c1e88fee5ff7de035fa16fbb04 (diff) | |
download | linuxcnc-7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4.tar.gz linuxcnc-7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4.zip |
python plugin: first cut of generic Python plugin
now independent of Interp
-rw-r--r-- | src/emc/pythonplugin/README | 2 | ||||
-rw-r--r-- | src/emc/pythonplugin/Submakefile | 38 | ||||
-rw-r--r-- | src/emc/pythonplugin/python_plugin.cc | 290 | ||||
-rw-r--r-- | src/emc/pythonplugin/python_plugin.hh | 56 |
4 files changed, 386 insertions, 0 deletions
diff --git a/src/emc/pythonplugin/README b/src/emc/pythonplugin/README new file mode 100644 index 000000000..07834ec6d --- /dev/null +++ b/src/emc/pythonplugin/README @@ -0,0 +1,2 @@ +spun out as separate class since it's used by interp and task +no reason to keep it an interp method diff --git a/src/emc/pythonplugin/Submakefile b/src/emc/pythonplugin/Submakefile new file mode 100644 index 000000000..e3badf91e --- /dev/null +++ b/src/emc/pythonplugin/Submakefile @@ -0,0 +1,38 @@ +PY=python2.6 +BOOST=boost_python + + +INCLUDES += emc/pythonplugin + +LIBPPSRCS := $(addprefix emc/pythonplugin/, \ + python_plugin.cc) + +USERSRCS += $(LIBPPSRCS) + +TESTSRCS := $(addprefix emc/pythonplugin/, \ + testpp.cc ) + +USERSRCS += $(TESTSRCS) + +$(call TOOBJSDEPS, $(LIBPPSRCS)) : EXTRAFLAGS=-fPIC -DBOOST_DEBUG_PYTHON -g -O0 + + +$(call TOOBJSDEPS, $(TESTSRCS)) : EXTRAFLAGS=-fPIC -DBOOST_DEBUG_PYTHON -g -O0 + +TARGETS += ../lib/libpyplugin.so emc/pythonplugin/testpp + +../lib/libpyplugin.so.0: $(patsubst %.cc,objects/%.o,$(LIBPPSRCS)) ../lib/libemcini.so + $(ECHO) Linking $(notdir $@) + @mkdir -p ../lib + @rm -f $@ + $(CXX) -g $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^ -lstdc++ -l$(BOOST) -l$(PY) + + +../include/%.h: ./emc/pythonplugin/%.h + cp $^ $@ +../include/%.hh: ./emc/pythonplugin/%.hh + cp $^ $@ + +emc/pythonplugin/testpp: $(call TOOBJS, $(TESTSRCS)) ../lib/libpyplugin.so.0 + $(ECHO) Linking $(notdir $@) + $(CXX) $(LDFLAGS) -o $@ $^ -lstdc++ -l$(BOOST) -l$(PY) diff --git a/src/emc/pythonplugin/python_plugin.cc b/src/emc/pythonplugin/python_plugin.cc new file mode 100644 index 000000000..8ca17867d --- /dev/null +++ b/src/emc/pythonplugin/python_plugin.cc @@ -0,0 +1,290 @@ + + +#include "python_plugin.hh" + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <exception> + +#define MAX_ERRMSG_SIZE 200 + +#define ERRMSG(fmt, args...) \ + do { \ + char msgbuf[MAX_ERRMSG_SIZE]; \ + snprintf(msgbuf, sizeof(msgbuf) -1, fmt, ##args); \ + error_msg = std::string(msgbuf); \ + } while(0) + + +#define PYCHK(bad, fmt, ...) \ + do { \ + if (bad) { \ + fprintf(stderr,fmt, ## __VA_ARGS__); \ + fprintf(stderr,"\n"); \ + ERRMSG(fmt, ## __VA_ARGS__); \ + return PLUGIN_ERROR; \ + } \ + } while(0) + +#define logPP(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) + +PythonPlugin::PythonPlugin(int loglevel) +{ + module_status = PYMOD_NONE; + log_level = loglevel; +} + +PythonPlugin::~PythonPlugin() +{ + // boost.python dont support PyFinalize() +} + + +int PythonPlugin::setup(const char *modpath, const char *modname , bool reload_if_changed) +{ + reload_on_change = reload_if_changed; + PYCHK((modname == NULL), "initialize: no module defined"); + module = modname; // ??? + if (modpath) { + strcpy(module_path, modpath); + strcat(module_path,"/"); + } else { + module_path[0] = '\0'; + } + strcat(module_path, modname); + strcat(module_path,".py"); + + char path[PATH_MAX]; + PYCHK(((realpath(module_path, path)) == NULL), + "setup: cant resolve path to '%s'", module_path); + + // record timestamp + struct stat st; + PYCHK(stat(module_path, &st), + "setup(): stat(%s) returns %s", module_path, strerror(errno)); + module_mtime = st.st_mtime; + Py_SetProgramName(module_path); + return PLUGIN_OK; +} + +int PythonPlugin::add_inittab_entry(const char *mod_name, void (*mod_init)()) +{ + PYCHK(PyImport_AppendInittab( (char *) mod_name , mod_init), + "cant extend inittab with module '%s'", mod_name); + modules.push_back(mod_name); + return PLUGIN_OK; +} + +int PythonPlugin::initialize(bool reload) +{ + std::string msg; + + if (!reload) { + Py_Initialize(); + if (module_path[0]) { + char pathcmd[PATH_MAX]; + sprintf(pathcmd, "import sys\nsys.path.append(\"%s\")", module); + PYCHK(PyRun_SimpleString(pathcmd), + "exeception running '%s'", pathcmd); + } + } + try { + bp::object module = bp::import("__main__"); + module_namespace = module.attr("__dict__"); + + for(unsigned i = 0; i < modules.size(); i++) { + module_namespace[modules[i]] = bp::import(modules[i].c_str()); + } + // FIXME + // the null deallocator avoids destroying the Interp instance on leaving scope or shutdown + // bp::scope(interp_module).attr("interp") = + // interp_ptr(this, interpDeallocFunc); + bp::object result = bp::exec_file(module_path, + module_namespace, + module_namespace); + module_status = PYMOD_OK; + } + catch (bp::error_already_set) { + python_exception = true; + if (PyErr_Occurred()) { + exception_msg = handle_pyerror(); + } else + exception_msg = "unknown exception"; + bp::handle_exception(); + module_status = PYMOD_FAILED; + PyErr_Clear(); + } + PYCHK(python_exception, "initialize: module '%s' init failed: \n%s", + module_path, exception_msg.c_str()); + return PLUGIN_OK; +} + + +int PythonPlugin::run_string(const char *cmd, bp::object &retval) +{ + if (reload_on_change) + reload(); + try { + retval = bp::exec(cmd, module_namespace, module_namespace); + } + catch (bp::error_already_set) { + if (PyErr_Occurred()) { + exception_msg = handle_pyerror(); + } else + exception_msg = "unknown exception"; + python_exception = true; + bp::handle_exception(); + PyErr_Clear(); + } + PYCHK(python_exception, "run_string(%s): \n%s", + cmd, exception_msg.c_str()); + return PLUGIN_OK; +} + +int PythonPlugin::call(const char *module, const char *callable, + bp::object tupleargs, bp::object kwargs, bp::object &retval) +{ + bp::object function; + + if (reload_on_change) + reload(); + if ((module_status != PYMOD_OK) || + (callable == NULL)) + return PLUGIN_ERROR; + + try { + if (module == NULL) { // default to function in toplevel module + function = module_namespace[callable]; + } else { + bp::object submod = module_namespace[module]; + bp::object submod_namespace = submod.attr("__dict__"); + function = submod_namespace[callable]; + } + retval = function(tupleargs,kwargs); + } + catch (bp::error_already_set) { + if (PyErr_Occurred()) { + exception_msg = handle_pyerror(); + } else + exception_msg = "unknown exception"; + python_exception = true; + bp::handle_exception(); + PyErr_Clear(); + } + PYCHK(python_exception, "call(%s.%s): \n%s", + module, callable, exception_msg.c_str()); + + return PLUGIN_OK; +} + +bool PythonPlugin::is_callable(const char *module, + const char *funcname) +{ + bool unexpected = false; + bool result = false; + bp::object function; + + if (reload_on_change) + reload(); + if ((module_status != PYMOD_OK) || + (funcname == NULL)) { + return false; + } + try { + if (module == NULL) { // default to function in toplevel module + function = module_namespace[funcname]; + } else { + bp::object submod = module_namespace[module]; + bp::object submod_namespace = submod.attr("__dict__"); + function = submod_namespace[funcname]; + } + result = PyCallable_Check(function.ptr()); + } + catch (bp::error_already_set) { + // KeyError expected if not callable + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + // something else, strange + exception_msg = handle_pyerror(); + unexpected = true; + } + result = false; + PyErr_Clear(); + } + if (unexpected) + logPP("is_pycallable(%s.%s): unexpected exception:\n%s",module,funcname,exception_msg.c_str()); + + if (log_level) + logPP("is_pycallable(%s.%s) = %s", module ? module : "",funcname,result ? "TRUE":"FALSE"); + return result; + + + return false; +} + +int PythonPlugin::reload() +{ + struct stat st; + if (module == NULL) + return PLUGIN_OK; + + if (stat(module_path, &st)) { + logPP("reload: stat(%s) returned %s", module_path, strerror(errno)); + return PLUGIN_ERROR; + } + if (st.st_mtime > module_mtime) { + module_mtime = st.st_mtime; + int status; + if ((status = initialize(true)) != PLUGIN_OK) { + // // init_python() set the error text already + // char err_msg[LINELEN+1]; + // error_text(status, err_msg, sizeof(err_msg)); + // logPP("reload(%s): %s", module_path, err_msg); + return PLUGIN_ERROR; + } else + logPP("reload(): module %s reloaded", module); + } + return PLUGIN_OK; +} + +int PythonPlugin::plugin_status() +{ + return PLUGIN_OK; + +} + +std::string PythonPlugin::last_exception() +{ + return exception_msg; +} + +std::string PythonPlugin::last_errmsg() +{ + return error_msg; +} + + +// decode a Python exception into a string. +std::string PythonPlugin::handle_pyerror() +{ + using namespace boost::python; + using namespace boost; + + PyObject *exc,*val,*tb; + object formatted_list, formatted; + PyErr_Fetch(&exc,&val,&tb); + handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb)); + object traceback(import("traceback")); + if (!tb) { + object format_exception_only(traceback.attr("format_exception_only")); + formatted_list = format_exception_only(hexc,hval); + } else { + object format_exception(traceback.attr("format_exception")); + formatted_list = format_exception(hexc,hval,htb); + } + formatted = str("\n").join(formatted_list); + return extract<std::string>(formatted); +} diff --git a/src/emc/pythonplugin/python_plugin.hh b/src/emc/pythonplugin/python_plugin.hh new file mode 100644 index 000000000..70f8a4802 --- /dev/null +++ b/src/emc/pythonplugin/python_plugin.hh @@ -0,0 +1,56 @@ +#ifndef PYTHON_PLUGIN_HH +#define PYTHON_PLUGIN_HH + +#include <boost/python.hpp> +namespace bp = boost::python; + +#include <vector> +#include <string> +#include <sys/types.h> +// #include <sys/stat.h> + +// PY_EXCEPTION: both exception_msg and error_msg are set +// PY_ERROR: error_msg is set + +enum py_retcode {PLUGIN_OK=0, PLUGIN_EXCEPTION=1, PLUGIN_ERROR=2, PLUGIN_NOTCALLABLE=3}; + +enum pymod_stat {PYMOD_NONE=0, PYMOD_FAILED=1,PYMOD_OK=2}; + +class PythonPlugin { + + +public: + PythonPlugin(int loglevel = 0); + ~PythonPlugin(); + int setup(const char *modpath, const char *module, bool reload_if_changed = false); + int add_inittab_entry(const char *mod_name, void (*mod_init)()); + int initialize(bool reload = false); + int run_string(const char *cmd, bp::object &retval); + int call(const char *module,const char *callable, + bp::object tupleargs, bp::object kwargs, bp::object &retval); + bool is_callable(const char *module, const char *funcname); + int plugin_status(); + + std::string last_exception(); + std::string last_errmsg(); + +private: + int reload(); + std::string handle_pyerror(); + + std::vector<std::string> modules; + bool python_exception; + int module_status; + bool reload_on_change; // auto-reload if toplevel module was changed + const char *py_dir; // plugin directory + const char *module; // toplevel module + char module_path[PATH_MAX]; + time_t module_mtime; // top level module - last modification time + bp::object module_namespace; + + std::string exception_msg; + std::string error_msg; + int log_level; +}; + +#endif |