summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Haberler <git@mah.priv.at>2011-07-21 00:14:12 +0200
committerMichael Haberler <git@mah.priv.at>2011-10-28 08:25:52 +0200
commit7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4 (patch)
treeec1a4b16877b7b42ffa7a85093020d2272f8abf3
parent1b14351210a3a5c1e88fee5ff7de035fa16fbb04 (diff)
downloadlinuxcnc-7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4.tar.gz
linuxcnc-7bb49b0895a8e1de6a53ce3f990b84a1274ebfd4.zip
python plugin: first cut of generic Python plugin
now independent of Interp
-rw-r--r--src/emc/pythonplugin/README2
-rw-r--r--src/emc/pythonplugin/Submakefile38
-rw-r--r--src/emc/pythonplugin/python_plugin.cc290
-rw-r--r--src/emc/pythonplugin/python_plugin.hh56
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