From ac5540ba873194b0bbd34439f5bd46b999ebc5e5 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 15 Jul 2019 20:02:18 +0100 Subject: [PATCH 1/3] Forward Python stdout and stderr to qDebug and qCritical --- src/runner/pythonrunner.cpp | 45 ++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 36b45a6..ca908aa 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -1299,6 +1299,43 @@ PythonRunner::PythonRunner(const MOBase::IOrganizer *moInfo) static const char *argv0 = "ModOrganizer.exe"; +struct PrintWrapper +{ + void write(const char * message) + { + buffer << message; + if (buffer.tellp() != 0 && buffer.str().back() == '\n') + { + qDebug().nospace().noquote() << buffer.str().substr(0, buffer.str().length() - 1).c_str(); + buffer = std::stringstream(); + } + } + + std::stringstream buffer; +}; + +struct ErrWrapper +{ + void write(const char * message) + { + buffer << message; + if (buffer.tellp() != 0 && buffer.str().back() == '\n') + { + qCritical().nospace().noquote() << buffer.str().substr(0, buffer.str().length() - 1).c_str(); + buffer = std::stringstream(); + } + } + + std::stringstream buffer; +}; + +BOOST_PYTHON_MODULE(moprivate) +{ + bpy::class_("PrintWrapper", bpy::init<>()) + .def("write", &PrintWrapper::write); + bpy::class_("ErrWrapper", bpy::init<>()) + .def("write", &ErrWrapper::write); +} bool PythonRunner::initPython(const QString &pythonPath) { @@ -1317,6 +1354,7 @@ bool PythonRunner::initPython(const QString &pythonPath) Py_SetProgramName(argBuffer); PyImport_AppendInittab("mobase", &PyInit_mobase); + PyImport_AppendInittab("moprivate", &PyInit_moprivate); Py_OptimizeFlag = 2; Py_NoSiteFlag = 1; initPath(); @@ -1331,11 +1369,12 @@ bool PythonRunner::initPython(const QString &pythonPath) bpy::object mainModule = bpy::import("__main__"); bpy::object mainNamespace = mainModule.attr("__dict__"); mainNamespace["sys"] = bpy::import("sys"); + mainNamespace["moprivate"] = bpy::import("moprivate"); bpy::import("site"); - mainNamespace["io"] = bpy::import("io"); - bpy::exec("s_ErrIO = io.StringIO()\n" - "sys.stderr = s_ErrIO", + bpy::exec("sys.stdout = moprivate.PrintWrapper()\n" + "sys.stderr = moprivate.ErrWrapper()\n", mainNamespace); + return true; } catch (const bpy::error_already_set&) { qDebug("failed to init python"); From 19dcdd95c08f55b14e4c4d6e20721408c122560a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 15 Jul 2019 20:04:44 +0100 Subject: [PATCH 2/3] Prevent PyQt from using its buggy exception logging by making it look like we're using custom exception handling. --- src/runner/pythonrunner.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index ca908aa..cf2a8dd 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -1372,7 +1372,8 @@ bool PythonRunner::initPython(const QString &pythonPath) mainNamespace["moprivate"] = bpy::import("moprivate"); bpy::import("site"); bpy::exec("sys.stdout = moprivate.PrintWrapper()\n" - "sys.stderr = moprivate.ErrWrapper()\n", + "sys.stderr = moprivate.ErrWrapper()\n" + "sys.excepthook = lambda x, y, z: sys.__excepthook__(x, y, z)", mainNamespace); return true; From 63a6c504b0bbfaafaf185ca4934d9a441eb71915 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 22 Jul 2019 00:08:35 +0100 Subject: [PATCH 3/3] Restore logging of historically-logged Python exceptions. --- src/runner/error.cpp | 55 +++++++++++++++++++++++++++++++------ src/runner/error.h | 19 +++++++++++++ src/runner/pythonrunner.cpp | 29 ++++++++----------- 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/runner/error.cpp b/src/runner/error.cpp index 62a2346..e17cd14 100644 --- a/src/runner/error.cpp +++ b/src/runner/error.cpp @@ -1,8 +1,9 @@ #ifndef Q_MOC_RUN #include #endif -#include #include +#include +#include "error.h" using namespace MOBase; namespace bpy = boost::python; @@ -10,17 +11,55 @@ namespace bpy = boost::python; void reportPythonError() { if (PyErr_Occurred()) { - // prints to s_ErrIO buffer + ErrWrapper &errWrapper = ErrWrapper::instance(); + + errWrapper.startRecordingExceptionMessage(); PyErr_Print(); - // extract data from python buffer - bpy::object mainModule = bpy::import("__main__"); - bpy::object mainNamespace = mainModule.attr("__dict__"); - bpy::object errMsgObj = bpy::eval("s_ErrIO.getvalue()", mainNamespace); - QString errMsg = bpy::extract(errMsgObj.ptr()); - bpy::eval("s_ErrIO.truncate(0)", mainNamespace); + errWrapper.stopRecordingExceptionMessage(); + + QString errMsg = errWrapper.getLastExceptionMessage(); throw MyException(errMsg); } else { throw MyException("An unexpected C++ exception was thrown in python code"); } } + +ErrWrapper & ErrWrapper::instance() +{ + static ErrWrapper err; + return err; +} + +void ErrWrapper::write(const char * message) +{ + buffer << message; + if (buffer.tellp() != 0 && buffer.str().back() == '\n') + { + // actually put the string in a variable so it doesn't get destroyed as soon as we get a pointer to its data + std::string string = buffer.str().substr(0, buffer.str().length() - 1); + qCritical().nospace().noquote() << string.c_str(); + buffer = std::stringstream(); + } + + if (recordingExceptionMessage) + { + lastException << message; + } +} + +void ErrWrapper::startRecordingExceptionMessage() +{ + recordingExceptionMessage = true; + lastException = std::stringstream(); +} + +void ErrWrapper::stopRecordingExceptionMessage() +{ + recordingExceptionMessage = false; +} + +QString ErrWrapper::getLastExceptionMessage() +{ + return QString::fromStdString(lastException.str()); +} diff --git a/src/runner/error.h b/src/runner/error.h index 5d3d6ff..2cadb57 100644 --- a/src/runner/error.h +++ b/src/runner/error.h @@ -1,7 +1,26 @@ #ifndef ERROR_H #define ERROR_H +#include +#include // turn an error from the python interpreter into an exception void reportPythonError(); +struct ErrWrapper +{ + static ErrWrapper & instance(); + + void write(const char * message); + + void startRecordingExceptionMessage(); + + void stopRecordingExceptionMessage(); + + QString getLastExceptionMessage(); + + std::stringstream buffer; + bool recordingExceptionMessage; + std::stringstream lastException; +}; + #endif // ERROR_H diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index cf2a8dd..5c8d91f 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -1306,7 +1306,9 @@ struct PrintWrapper buffer << message; if (buffer.tellp() != 0 && buffer.str().back() == '\n') { - qDebug().nospace().noquote() << buffer.str().substr(0, buffer.str().length() - 1).c_str(); + // actually put the string in a variable so it doesn't get destroyed as soon as we get a pointer to its data + std::string string = buffer.str().substr(0, buffer.str().length() - 1); + qDebug().nospace().noquote() << string.c_str(); buffer = std::stringstream(); } } @@ -1314,27 +1316,18 @@ struct PrintWrapper std::stringstream buffer; }; -struct ErrWrapper -{ - void write(const char * message) - { - buffer << message; - if (buffer.tellp() != 0 && buffer.str().back() == '\n') - { - qCritical().nospace().noquote() << buffer.str().substr(0, buffer.str().length() - 1).c_str(); - buffer = std::stringstream(); - } - } - - std::stringstream buffer; -}; +// ErrWrapper is in error.h BOOST_PYTHON_MODULE(moprivate) { bpy::class_("PrintWrapper", bpy::init<>()) .def("write", &PrintWrapper::write); bpy::class_("ErrWrapper", bpy::init<>()) - .def("write", &ErrWrapper::write); + .def("instance", &ErrWrapper::instance, bpy::return_value_policy()).staticmethod("instance") + .def("write", &ErrWrapper::write) + .def("startRecordingExceptionMessage", &ErrWrapper::startRecordingExceptionMessage) + .def("stopRecordingExceptionMessage", &ErrWrapper::stopRecordingExceptionMessage) + .def("getLastExceptionMessage", &ErrWrapper::getLastExceptionMessage); } bool PythonRunner::initPython(const QString &pythonPath) @@ -1372,8 +1365,8 @@ bool PythonRunner::initPython(const QString &pythonPath) mainNamespace["moprivate"] = bpy::import("moprivate"); bpy::import("site"); bpy::exec("sys.stdout = moprivate.PrintWrapper()\n" - "sys.stderr = moprivate.ErrWrapper()\n" - "sys.excepthook = lambda x, y, z: sys.__excepthook__(x, y, z)", + "sys.stderr = moprivate.ErrWrapper.instance()\n" + "sys.excepthook = lambda x, y, z: sys.__excepthook__(x, y, z)\n", mainNamespace); return true;