Multiple fixes.

This commit is contained in:
Mikaël Capelle
2022-04-28 13:44:29 +02:00
parent d0e883fd90
commit 4c7b452219
5 changed files with 109 additions and 72 deletions
+10 -10
View File
@@ -14,48 +14,48 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="207"/>
<location filename="proxypython.cpp" line="202"/>
<source>ModOrganizer path contains a semicolon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="209"/>
<location filename="proxypython.cpp" line="204"/>
<source>Python DLL not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="211"/>
<location filename="proxypython.cpp" line="206"/>
<source>Invalid Python DLL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="213"/>
<location filename="proxypython.cpp" line="208"/>
<source>Initializing Python failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="215"/>
<location filename="proxypython.cpp" line="245"/>
<location filename="proxypython.cpp" line="210"/>
<location filename="proxypython.cpp" line="240"/>
<source>invalid problem key %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="223"/>
<location filename="proxypython.cpp" line="218"/>
<source>The path to Mod Organizer (%1) contains a semicolon. &lt;br&gt;While this is legal on NTFS drives, many softwares do not handle it correctly.&lt;br&gt;Unfortunately MO depends on libraries that seem to fall into that group.&lt;br&gt;As a result the python plugin cannot be loaded, and the only solution we canoffer is to remove the semicolon or move MO to a path without a semicolon.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="234"/>
<location filename="proxypython.cpp" line="229"/>
<source>The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="237"/>
<location filename="proxypython.cpp" line="232"/>
<source>The Python plugin DLL is invalid, maybe your antivirus is blocking it. Re-installing MO2 and adding exclusions for it to your AV might fix the problem.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="242"/>
<location filename="proxypython.cpp" line="237"/>
<source>The initialization of the Python plugin DLL failed, unfortunately without any details.</source>
<translation type="unfinished"></translation>
</message>
+1 -6
View File
@@ -169,12 +169,7 @@ QList<QObject*> ProxyPython::load(const QString& identifier)
if (!m_Runner) {
return {};
}
auto plugins = m_Runner->load(identifier);
for (auto* plugin : plugins) {
plugin->setParent(this);
}
return plugins;
return m_Runner->load(identifier);
}
void ProxyPython::unload(const QString& identifier)
@@ -10,6 +10,33 @@
namespace pybind11::qt {
/**
* @brief Tie the lifetime of the Python object to the lifetime of the given
* QObject.
*
* @param owner QObject that will own the python object.
* @param child Python object that the QObject will own.
*/
inline void set_owner(QObject* owner, object child)
{
new detail::qt::qobject_holder{owner, child};
}
/**
* @brief Tie the lifetime of the given object to the lifetime of the corresponding
* Python object.
*
* This object must have been created from Python and must inherit QObject.
*
* @param object Object to tie.
*/
template <typename Class>
void set_owner(Class* object)
{
static_assert(std::is_base_of_v<QObject, Class>);
new detail::qt::qobject_holder{object};
}
/**
* @brief Add Qt "delegate" to the given class.
*
@@ -11,10 +11,18 @@ namespace pybind11::detail::qt {
object p_;
public:
/**
* @brief Construct a new qobject holder linked to the given QObject and
* maintaining the given python object alive.
*
* @param p Parent of this holder.
* @param o Python object to keep alive.
*/
qobject_holder(QObject* p, object o) : p_{o} { setParent(p); }
template <class U>
qobject_holder(U* p) : p_{reinterpret_borrow<object>(cast(p))}
qobject_holder(U* p) : qobject_holder{p, reinterpret_borrow<object>(cast(p))}
{
setParent(p);
}
~qobject_holder()
+61 -54
View File
@@ -276,8 +276,8 @@ PYBIND11_MODULE(mobase, m)
class PythonRunner : public IPythonRunner {
public:
PythonRunner();
~PythonRunner();
PythonRunner() = default;
~PythonRunner() = default;
bool initPython();
@@ -296,9 +296,10 @@ private:
void ensureFolderInPath(QString folder);
private:
// For each "identifier" (python file or python module folder), contains the
// list of python objects to keep "alive" during the execution.
std::unordered_map<QString, std::vector<py::object>> m_PythonObjects;
// for each "identifier" (python file or python module folder), contains the
// list of python objects - this does not keep the objects alive, it simply used to
// unload plugins
std::unordered_map<QString, std::vector<py::handle>> m_PythonObjects;
};
IPythonRunner* CreatePythonRunner()
@@ -312,30 +313,6 @@ IPythonRunner* CreatePythonRunner()
}
}
PythonRunner::PythonRunner() {}
PythonRunner::~PythonRunner()
{
// Boost.Python does not handle cyclic garbace collection, so we need to release
// everything hold by the objects before deleting the objects themselves:
// for (auto& [name, objects] : m_PythonObjects) {
// for (auto& obj : objects) {
// obj.attr("__dict__").attr("clear")();
// }
// }
// we need to clear this here otherwise there is a crash in
// finalize_interpreter()
// {
// py::gil_scoped_acquire s;
// m_PythonObjects.clear();
// }
// py::finalize_interpreter();
}
// ErrWrapper is in error.h
PYBIND11_MODULE(moprivate, m)
{
// expose a function to create a particular tree, only for debugging
@@ -352,17 +329,31 @@ bool PythonRunner::initPython()
try {
static const char* argv0 = "ModOrganizer.exe";
PyImport_AppendInittab("mobase", &PyInit_mobase);
PyImport_AppendInittab("moprivate", &PyInit_moprivate);
initPath();
if (PyImport_AppendInittab("mobase", &PyInit_mobase) == -1) {
MOBase::log::error("failed to init python: failed to append mobase.");
return false;
}
if (PyImport_AppendInittab("moprivate", &PyInit_moprivate) == -1) {
MOBase::log::error("failed to init python: failed to append moprivate.");
return false;
}
Py_OptimizeFlag = 2;
Py_NoSiteFlag = 1;
initPath();
py::initialize_interpreter(false, 1, &argv0);
if (!Py_IsInitialized()) {
MOBase::log::error(
"failed to init python: failed to initialize interpreter.");
if (PyGILState_Check()) {
PyEval_SaveThread();
}
return false;
}
@@ -375,6 +366,12 @@ bool PythonRunner::initPython()
mo2::python::configure_python_stream();
mo2::python::configure_python_logging(mainNamespace["mobase"]);
// we need to release the GIL here - which is what this does
//
// when Python is initialized, the GIl is acquired, and if it is not release,
// trying to acquire it on a different thread will deadlock
PyEval_SaveThread();
return true;
}
catch (const py::error_already_set& ex) {
@@ -383,16 +380,6 @@ bool PythonRunner::initPython()
}
}
void PythonRunner::initPath()
{
static QStringList paths = {QCoreApplication::applicationDirPath() +
"/pythoncore.zip",
QCoreApplication::applicationDirPath() + "/pythoncore",
IOrganizer::getPluginDataPath()};
Py_SetPath(paths.join(';').toStdWString().c_str());
}
void PythonRunner::ensureFolderInPath(QString folder)
{
py::module_ sys = py::module_::import("sys");
@@ -406,6 +393,16 @@ void PythonRunner::ensureFolderInPath(QString folder)
}
}
void PythonRunner::initPath()
{
static QStringList paths = {QCoreApplication::applicationDirPath() +
"/pythoncore.zip",
QCoreApplication::applicationDirPath() + "/pythoncore",
IOrganizer::getPluginDataPath()};
Py_SetPath(paths.join(';').toStdWString().c_str());
}
QList<QObject*> PythonRunner::load(const QString& identifier)
{
py::gil_scoped_acquire lock;
@@ -428,11 +425,14 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
py::dict moduleDict;
if (identifier.endsWith(".py")) {
py::object mainModule = py::module_::import("__main__");
py::dict moduleNamespace = mainModule.attr("__dict__");
py::object mainModule = py::module_::import("__main__");
// make a copy, otherwise we might end up calling the createPlugin() or
// createPlugins() function multiple time
py::dict moduleNamespace = mainModule.attr("__dict__").attr("copy")();
std::string temp = ToString(identifier);
py::eval_file(temp.c_str(), moduleNamespace).is_none();
py::eval_file(temp, moduleNamespace).is_none();
moduleDict = moduleNamespace;
}
else {
@@ -442,7 +442,15 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
ensureFolderInPath(parts.join("/"));
// check if the module is already loaded
moduleDict = py::module_::import(moduleName.c_str()).attr("__dict__");
py::dict modules = py::module_::import("sys").attr("modules");
if (modules.contains(moduleName)) {
py::module_ prev = modules[py::str(moduleName)];
py::module_(prev).reload();
moduleDict = prev.attr("__dict__");
}
else {
moduleDict = py::module_::import(moduleName.c_str()).attr("__dict__");
}
}
if (py::len(moduleDict) == 0) {
@@ -455,9 +463,6 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
if (moduleDict.contains("createPlugin")) {
plugins.push_back(moduleDict["createPlugin"]());
// Clear for future call
// PyDict_DelItemString(moduleDict.ptr(), "createPlugin");
}
else if (moduleDict.contains("createPlugins")) {
py::object pyPlugins = moduleDict["createPlugins"]();
@@ -472,9 +477,6 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
plugins.push_back(pyList[i]);
}
}
// Clear for future call
// PyDict_DelItemString(moduleDict.ptr(), "createPlugins");
}
else {
MOBase::log::error("Plugin {}: missing a createPlugin(s) function.",
@@ -491,7 +493,7 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
for (py::object pluginObj : plugins) {
// Add the plugin to keep it alive:
// save to be able to unload it
m_PythonObjects[identifier].push_back(pluginObj);
QList<QObject*> interfaceList = mo2::python::extract_plugins(pluginObj);
@@ -501,6 +503,11 @@ QList<QObject*> PythonRunner::load(const QString& identifier)
identifier);
}
// tie the lifetime of the Python object to the lifetime of the QObject
for (auto* object : interfaceList) {
py::qt::set_owner(object, pluginObj);
}
// Append the plugins to the main list:
allInterfaceList.append(interfaceList);
}