Update following uibase changes for extensions.

This commit is contained in:
Mikaël Capelle
2024-08-09 22:05:23 +02:00
parent 2d151d0f66
commit 8fa0d0ade8
7 changed files with 155 additions and 365 deletions
+11 -54
View File
@@ -4,78 +4,35 @@
<context>
<name>ProxyPython</name>
<message>
<location filename="proxy/proxypython.cpp" line="88"/>
<source>Python Initialization failed</source>
<location filename="proxy/proxypython.cpp" line="156"/>
<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="proxy/proxypython.cpp" line="89"/>
<source>On a previous start the Python Plugin failed to initialize.
Do you want to try initializing python again (at the risk of another crash)?
Suggestion: Select &quot;no&quot;, and click the warning sign for further help.Afterwards you have to re-enable the python plugin.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="162"/>
<source>Python Proxy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="172"/>
<source>Proxy Plugin to allow plugins written in python to be loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="244"/>
<source>ModOrganizer path contains a semicolon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="246"/>
<source>Python DLL not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="248"/>
<source>Invalid Python DLL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="250"/>
<source>Initializing Python failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="252"/>
<location filename="proxy/proxypython.cpp" line="281"/>
<source>invalid problem key %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="260"/>
<source>The path to Mod Organizer (%1) contains a semicolon.&lt;br&gt;While this is legal on NTFS drives, many applications 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 can offer is to remove the semicolon or move MO to a path without a semicolon.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="270"/>
<location filename="proxy/proxypython.cpp" line="167"/>
<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="proxy/proxypython.cpp" line="273"/>
<location filename="proxy/proxypython.cpp" line="170"/>
<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="proxy/proxypython.cpp" line="278"/>
<location filename="proxy/proxypython.cpp" line="174"/>
<source>The initialization of the Python plugin DLL failed, unfortunately without any details.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="177"/>
<source>no failure</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="runner/error.h" line="76"/>
<location filename="runner/error.h" line="77"/>
<source>An unknown exception was thrown in python code.</source>
<translation type="unfinished"></translation>
</message>
+14 -9
View File
@@ -2,28 +2,33 @@ cmake_minimum_required(VERSION 3.16)
find_package(mo2-uibase CONFIG REQUIRED)
set(PLUGIN_NAME "plugin_python")
set(PROXY_NAME "python")
add_library(proxy SHARED proxypython.cpp proxypython.h)
mo2_configure_plugin(proxy
NO_SOURCES
WARNINGS 4
EXTERNAL_WARNINGS 4
TRANSLATIONS OFF
TRANSLATIONS ON
EXTRA_TRANSLATIONS
${CMAKE_CURRENT_SOURCE_DIR}/../runner
${CMAKE_CURRENT_SOURCE_DIR}/../mobase
${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
mo2_default_source_group()
target_link_libraries(proxy PRIVATE runner mo2::uibase)
set_target_properties(proxy PROPERTIES OUTPUT_NAME ${PLUGIN_NAME})
mo2_install_plugin(proxy FOLDER)
set_target_properties(proxy PROPERTIES OUTPUT_NAME "python_proxy")
set(PLUGIN_PYTHON_DIR bin/plugins/${PLUGIN_NAME})
set(PROXY_PYTHON_DIR ${MO2_INSTALL_BIN}/proxies/python)
# install runner
# install runner and proxy
install(FILES $<TARGET_FILE:proxy> DESTINATION ${PROXY_PYTHON_DIR}/python)
install(FILES $<TARGET_FILE:runner> DESTINATION ${PROXY_PYTHON_DIR}/python/dlls)
# install PDB
install(FILES $<TARGET_PDB_FILE:proxy> DESTINATION pdb)
# delay loading since the dll is not in the standard folder
target_link_options(proxy PRIVATE "/DELAYLOAD:runner.dll")
install(FILES $<TARGET_FILE:runner> DESTINATION ${PLUGIN_PYTHON_DIR}/dlls)
# translations (custom location)
mo2_add_translations(proxy
@@ -35,14 +40,14 @@ mo2_add_translations(proxy
${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
# install DLLs files needed
set(DLL_DIRS ${PLUGIN_PYTHON_DIR}/dlls)
set(DLL_DIRS ${PROXY_PYTHON_DIR}/dlls)
file(GLOB dlls_to_install
# ${PYTHON_BUILD_PATH}/libffi*.dll
${Python_HOME}/python${Python_VERSION_MAJOR}*.dll)
install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS})
# install Python .pyd files
set(PYLIB_DIR ${PLUGIN_PYTHON_DIR}/libs)
set(PYLIB_DIR ${PROXY_PYTHON_DIR}/libs)
file(GLOB libs_to_install ${Python_DLL_DIR}/*.pyd)
install(FILES ${libs_to_install} DESTINATION ${PYLIB_DIR})
+51 -149
View File
@@ -53,59 +53,26 @@ fs::path getPluginFolder()
return fs::path(path).parent_path();
}
ProxyPython::ProxyPython()
: m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr},
m_LoadFailure(FailureType::NONE)
{
}
ProxyPython::ProxyPython() : m_RunnerLib{nullptr}, m_Runner{nullptr} {}
bool ProxyPython::init(IOrganizer* moInfo)
bool ProxyPython::initialize(QString& errorMessage)
{
m_MOInfo = moInfo;
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
return false;
}
errorMessage = "";
if (QCoreApplication::applicationDirPath().contains(';')) {
m_LoadFailure = FailureType::SEMICOLON;
errorMessage = failureMessage(FailureType::SEMICOLON);
return true;
}
const auto pluginFolder = getPluginFolder();
if (pluginFolder.empty()) {
DWORD error = ::GetLastError();
m_LoadFailure = FailureType::DLL_NOT_FOUND;
DWORD error = ::GetLastError();
errorMessage = failureMessage(FailureType::DLL_NOT_FOUND);
log::error("failed to resolve Python proxy directory ({}): {}", error,
qUtf8Printable(windowsErrorString(::GetLastError())));
return false;
}
if (m_MOInfo && m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
m_LoadFailure = FailureType::INITIALIZATION;
if (QMessageBox::question(
parentWidget(), tr("Python Initialization failed"),
tr("On a previous start the Python Plugin failed to initialize.\n"
"Do you want to try initializing python again (at the risk of "
"another crash)?\n "
"Suggestion: Select \"no\", and click the warning sign for further "
"help.Afterwards you have to re-enable the python plugin."),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::No) {
// we force enabled here (note: this is a persistent settings since MO2 2.4
// or something), plugin
// usually should not handle enabled/disabled themselves but this is a base
// plugin so...
m_MOInfo->setPersistent(name(), "enabled", false, true);
return true;
}
}
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", true);
}
// load the pythonrunner library, this is done in multiple steps:
//
// 1. we set the dlls/ subfolder (from the plugin) as the DLL directory so Windows
@@ -114,8 +81,8 @@ bool ProxyPython::init(IOrganizer* moInfo)
//
const auto dllPaths = pluginFolder / "dlls";
if (SetDllDirectoryW(dllPaths.c_str()) == 0) {
DWORD error = ::GetLastError();
m_LoadFailure = FailureType::DLL_NOT_FOUND;
DWORD error = ::GetLastError();
errorMessage = failureMessage(FailureType::DLL_NOT_FOUND);
log::error("failed to add python DLL directory ({}): {}", error,
qUtf8Printable(windowsErrorString(::GetLastError())));
return false;
@@ -129,21 +96,16 @@ bool ProxyPython::init(IOrganizer* moInfo)
if (m_Runner) {
const auto libpath = pluginFolder / "libs";
const std::vector<fs::path> paths{
libpath / "pythoncore.zip", libpath,
std::filesystem::path{IOrganizer::getPluginDataPath().toStdWString()}};
const std::vector<fs::path> paths{libpath / "pythoncore.zip", libpath};
m_Runner->initialize(paths);
}
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", false);
}
// reset DLL directory
SetDllDirectoryW(NULL);
if (!m_Runner || !m_Runner->isInitialized()) {
m_LoadFailure = FailureType::INITIALIZATION;
errorMessage = failureMessage(FailureType::INITIALIZATION);
return false;
}
else {
m_Runner->addDllSearchPath(pluginFolder / "dlls");
@@ -152,110 +114,57 @@ bool ProxyPython::init(IOrganizer* moInfo)
return true;
}
QString ProxyPython::name() const
{
return "Python Proxy";
}
QString ProxyPython::localizedName() const
{
return tr("Python Proxy");
}
QString ProxyPython::author() const
{
return "AnyOldName3, Holt59, Silarn, Tannin";
}
QString ProxyPython::description() const
{
return tr("Proxy Plugin to allow plugins written in python to be loaded");
}
VersionInfo ProxyPython::version() const
{
return VersionInfo(3, 0, 0, VersionInfo::RELEASE_FINAL);
}
QList<PluginSetting> ProxyPython::settings() const
{
return {};
}
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
{
QDir dir(pluginPath);
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
QDirIterator iter(dir);
// Note: We put python script (.py) and directory names, not the __init__.py
// files in those since it is easier for the runner to import them.
QStringList result;
while (iter.hasNext()) {
QString name = iter.next();
QFileInfo info = iter.fileInfo();
if (info.isFile() && name.endsWith(".py")) {
result.append(name);
}
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
result.append(name);
}
}
return result;
}
QList<QObject*> ProxyPython::load(const QString& identifier)
QList<QList<QObject*>> ProxyPython::load(const PluginExtension& extension)
{
if (!m_Runner) {
return {};
}
return m_Runner->load(identifier);
// currently, only handle __init__.py directly in the folder
const auto pyIniFile = extension.directory() / "__init__.py";
if (!exists(pyIniFile)) {
return {};
}
m_ExtensionModules[&extension] = {pyIniFile};
QList<QList<QObject*>> plugins;
for (auto&& pythonModule : m_ExtensionModules[&extension]) {
plugins.append(m_Runner->load(pythonModule));
}
return plugins;
}
void ProxyPython::unload(const QString& identifier)
void ProxyPython::unload(const PluginExtension& extension)
{
if (!m_Runner) {
return;
}
if (auto it = m_ExtensionModules.find(&extension); it != m_ExtensionModules.end()) {
for (auto&& pythonModule : it->second) {
m_Runner->unload(pythonModule);
}
m_ExtensionModules.erase(it);
}
}
void ProxyPython::unloadAll()
{
if (m_Runner) {
return m_Runner->unload(identifier);
for (auto& [ext, modules] : m_ExtensionModules) {
for (auto& pythonModule : modules) {
m_Runner->unload(pythonModule);
}
}
}
m_ExtensionModules.clear();
}
std::vector<unsigned int> ProxyPython::activeProblems() const
QString ProxyPython::failureMessage(FailureType key)
{
auto failure = m_LoadFailure;
// don't know how this could happen but wth
if (m_Runner && !m_Runner->isInitialized()) {
failure = FailureType::INITIALIZATION;
}
if (failure != FailureType::NONE) {
return {static_cast<std::underlying_type_t<FailureType>>(failure)};
}
return {};
}
QString ProxyPython::shortDescription(unsigned int key) const
{
switch (static_cast<FailureType>(key)) {
case FailureType::SEMICOLON:
return tr("ModOrganizer path contains a semicolon");
case FailureType::DLL_NOT_FOUND:
return tr("Python DLL not found");
case FailureType::INVALID_DLL:
return tr("Invalid Python DLL");
case FailureType::INITIALIZATION:
return tr("Initializing Python failed");
default:
return tr("invalid problem key %1").arg(key);
}
}
QString ProxyPython::fullDescription(unsigned int key) const
{
switch (static_cast<FailureType>(key)) {
switch (key) {
case FailureType::SEMICOLON:
return tr("The path to Mod Organizer (%1) contains a semicolon.<br>"
"While this is legal on NTFS drives, many applications do not "
@@ -278,13 +187,6 @@ QString ProxyPython::fullDescription(unsigned int key) const
return tr("The initialization of the Python plugin DLL failed, unfortunately "
"without any details.");
default:
return tr("invalid problem key %1").arg(key);
return tr("no failure");
}
}
bool ProxyPython::hasGuidedFix(unsigned int) const
{
return false;
}
void ProxyPython::startGuidedFix(unsigned int) const {}
+17 -28
View File
@@ -23,45 +23,27 @@ along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
#include <map>
#include <memory>
#include <uibase/extensions/ipluginloader.h>
#include <uibase/iplugindiagnose.h>
#include <uibase/ipluginproxy.h>
#include <Windows.h>
#include <pythonrunner.h>
class ProxyPython : public QObject,
public MOBase::IPluginProxy,
public MOBase::IPluginDiagnose {
class ProxyPython : public MOBase::IPluginLoader {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginProxy MOBase::IPluginDiagnose)
Q_INTERFACES(MOBase::IPluginLoader)
Q_PLUGIN_METADATA(IID "org.mo2.ProxyPython")
public:
ProxyPython();
virtual bool init(MOBase::IOrganizer* moInfo);
virtual QString name() const override;
virtual QString localizedName() const override;
virtual QString author() const override;
virtual QString description() const override;
virtual MOBase::VersionInfo version() const override;
virtual QList<MOBase::PluginSetting> settings() const override;
QStringList pluginList(const QDir& pluginPath) const override;
QList<QObject*> load(const QString& identifier) override;
void unload(const QString& identifier) override;
public: // IPluginDiagnose
virtual std::vector<unsigned int> activeProblems() const override;
virtual QString shortDescription(unsigned int key) const override;
virtual QString fullDescription(unsigned int key) const override;
virtual bool hasGuidedFix(unsigned int key) const override;
virtual void startGuidedFix(unsigned int key) const override;
bool initialize(QString& errorMessage) override;
QList<QList<QObject*>> load(const MOBase::PluginExtension& extension) override;
void unload(const MOBase::PluginExtension& extension) override;
void unloadAll() override;
private:
MOBase::IOrganizer* m_MOInfo;
HMODULE m_RunnerLib;
std::unique_ptr<mo2::python::IPythonRunner> m_Runner;
enum class FailureType : unsigned int {
NONE = 0,
SEMICOLON = 1,
@@ -70,7 +52,14 @@ private:
INITIALIZATION = 4
};
FailureType m_LoadFailure;
static QString failureMessage(FailureType failureType);
private:
HMODULE m_RunnerLib;
std::unique_ptr<mo2::python::IPythonRunner> m_Runner;
std::unordered_map<const MOBase::PluginExtension*,
std::vector<std::filesystem::path>>
m_ExtensionModules;
};
#endif // PROXYPYTHON_H
+2 -1
View File
@@ -21,7 +21,8 @@ target_link_libraries(runner PUBLIC mo2::uibase PRIVATE pybind11::embed pybind11
target_include_directories(runner PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(runner PRIVATE RUNNER_BUILD)
# proxy will install runner
# proxy will install runner but we install the PDB
install(FILES $<TARGET_PDB_FILE:runner> DESTINATION pdb)
# force runner to build mobase
add_dependencies(runner mobase)
+55 -122
View File
@@ -36,24 +36,12 @@ namespace mo2::python {
PythonRunner() = default;
~PythonRunner() = default;
QList<QObject*> load(const QString& identifier) override;
void unload(const QString& identifier) override;
QList<QList<QObject*>> load(std::filesystem::path const& pythonModule) override;
void unload(std::filesystem::path const& pythonModule) override;
bool initialize(std::vector<std::filesystem::path> const& pythonPaths) override;
void addDllSearchPath(std::filesystem::path const& dllPath) override;
bool isInitialized() const override;
private:
/**
* @brief Ensure that the given folder is in sys.path.
*/
void ensureFolderInPath(QString folder);
private:
// 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;
};
std::unique_ptr<IPythonRunner> createPythonRunner()
@@ -162,72 +150,41 @@ namespace mo2::python {
py::module_::import("os").attr("add_dll_directory")(absolute(dllPath));
}
void PythonRunner::ensureFolderInPath(QString folder)
{
py::module_ sys = py::module_::import("sys");
py::list sysPath = sys.attr("path");
// Converting to QStringList for Qt::CaseInsensitive and because .index()
// raise an exception:
const QStringList currentPath = sysPath.cast<QStringList>();
if (!currentPath.contains(folder, Qt::CaseInsensitive)) {
sysPath.insert(0, folder);
}
}
QList<QObject*> PythonRunner::load(const QString& identifier)
QList<QList<QObject*>> PythonRunner::load(const std::filesystem::path& pythonModule)
{
py::gil_scoped_acquire lock;
// `pluginName` can either be a python file (single-file plugin or a folder
// (whole module).
//
// For whole module, we simply add the parent folder to path, then we load
// the module with a simple py::import, and we retrieve the associated
// __dict__ from which we extract either createPlugin or createPlugins.
//
// For single file, we need to use py::eval_file, and we will use the
// context (global variables) from __main__ (already contains mobase, and
// other required module). Since the context is shared between called of
// `instantiate`, we need to make sure to remove createPlugin(s) from
// previous call.
try {
// dictionary that will contain createPlugin() or createPlugins().
py::dict moduleDict;
// some needed import
auto sys = py::module_::import("sys");
auto importlib_util = py::module_::import("importlib.util");
if (identifier.endsWith(".py")) {
py::object mainModule = py::module_::import("__main__");
// check the file type
const auto moduleName =
pythonModule.filename() == "__init__.py"
? pythonModule.parent_path().filename().u8string()
: pythonModule.filename().u8string();
// 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, moduleNamespace).is_none();
moduleDict = moduleNamespace;
// check if the module is already loaded
py::dict modules = sys.attr("modules");
py::module_ pymodule;
if (modules.contains(moduleName)) {
pymodule = modules[py::str(moduleName)];
pymodule.reload();
}
else {
// Retrieve the module name:
QStringList parts = identifier.split("/");
std::string moduleName = ToString(parts.takeLast());
ensureFolderInPath(parts.join("/"));
// check if the module is already loaded
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__");
}
// load the module
auto spec = importlib_util.attr("find_spec")(moduleName, pythonModule);
pymodule = importlib_util.attr("module_from_spec")(spec);
sys.attr("modules")[py::str(moduleName)] = pymodule;
spec.attr("loader").attr("exec_module")(pymodule);
}
py::dict moduleDict = pymodule.attr("__dict__");
if (py::len(moduleDict) == 0) {
MOBase::log::error("No plugins found in {}.", identifier);
MOBase::log::error("no plugins found in {}", pythonModule);
return {};
}
@@ -240,8 +197,8 @@ namespace mo2::python {
else if (moduleDict.contains("createPlugins")) {
py::object pyPlugins = moduleDict["createPlugins"]();
if (!py::isinstance<py::sequence>(pyPlugins)) {
MOBase::log::error(
"Plugin {}: createPlugins must return a sequence.", identifier);
MOBase::log::error("{}: createPlugins must return a sequence",
pythonModule);
}
else {
py::sequence pyList(pyPlugins);
@@ -252,30 +209,26 @@ namespace mo2::python {
}
}
else {
MOBase::log::error("Plugin {}: missing a createPlugin(s) function.",
identifier);
MOBase::log::error("{}: missing createPlugin(s) function",
pythonModule);
}
// If we have no plugins, there was an issue, and we already logged the
// problem:
// if we have no plugins, there was an issue, and we already logged the
// problem
if (plugins.empty()) {
return QList<QObject*>();
return {};
}
QList<QObject*> allInterfaceList;
QList<QList<QObject*>> allInterfaceList;
for (py::object pluginObj : plugins) {
// save to be able to unload it
m_PythonObjects[identifier].push_back(pluginObj);
QList<QObject*> interfaceList = py::module_::import("mobase.private")
.attr("extract_plugins")(pluginObj)
.cast<QList<QObject*>>();
if (interfaceList.isEmpty()) {
MOBase::log::error("Plugin {}: no plugin interface implemented.",
identifier);
MOBase::log::error("{}: no plugin interface implemented.",
pythonModule);
}
// Append the plugins to the main list:
@@ -285,57 +238,37 @@ namespace mo2::python {
return allInterfaceList;
}
catch (const py::error_already_set& ex) {
MOBase::log::error("Failed to import plugin from {}.", identifier);
MOBase::log::error("failed to import plugin from {}", pythonModule);
throw pyexcept::PythonError(ex);
}
}
void PythonRunner::unload(const QString& identifier)
void PythonRunner::unload(const std::filesystem::path& pythonModule)
{
auto it = m_PythonObjects.find(identifier);
if (it != m_PythonObjects.end()) {
py::gil_scoped_acquire lock;
py::gil_scoped_acquire lock;
// At this point, the identifier is the full path to the module.
QDir folder(pythonModule);
if (!identifier.endsWith(".py")) {
// we want to "unload" (remove from sys.modules) modules that come
// from this plugin (whose __path__ points under this module,
// including the module of the plugin itself)
py::object sys = py::module_::import("sys");
py::dict modules = sys.attr("modules");
py::list keys = modules.attr("keys")();
for (std::size_t i = 0; i < py::len(keys); ++i) {
py::object mod = modules[keys[i]];
if (PyObject_HasAttrString(mod.ptr(), "__path__")) {
QString mpath = mod.attr("__path__")[py::int_(0)].cast<QString>();
// At this point, the identifier is the full path to the module.
QDir folder(identifier);
if (!folder.relativeFilePath(mpath).startsWith("..")) {
// if the path is under identifier, we need to unload it
log::debug("unloading module {} from {}",
keys[i].cast<std::string>(), mpath);
// We want to "unload" (remove from sys.modules) modules that come
// from this plugin (whose __path__ points under this module,
// including the module of the plugin itself).
py::object sys = py::module_::import("sys");
py::dict modules = sys.attr("modules");
py::list keys = modules.attr("keys")();
for (std::size_t i = 0; i < py::len(keys); ++i) {
py::object mod = modules[keys[i]];
if (PyObject_HasAttrString(mod.ptr(), "__path__")) {
QString mpath =
mod.attr("__path__")[py::int_(0)].cast<QString>();
if (!folder.relativeFilePath(mpath).startsWith("..")) {
// If the path is under identifier, we need to unload
// it.
log::debug("Unloading module {} from {} for {}.",
keys[i].cast<std::string>(), mpath, identifier);
PyDict_DelItem(modules.ptr(), keys[i].ptr());
}
}
PyDict_DelItem(modules.ptr(), keys[i].ptr());
}
}
// Boost.Python does not handle cyclic garbace collection, so we need to
// release everything hold by the objects before deleting the objects
// themselves (done when erasing from m_PythonObjects).
for (auto& obj : it->second) {
obj.attr("__dict__").attr("clear")();
}
log::debug("Deleting {} python objects for {}.", it->second.size(),
identifier);
m_PythonObjects.erase(it);
}
}
+5 -2
View File
@@ -9,6 +9,8 @@
#include <QString>
#include <QStringList>
#include <extension.h>
#ifdef RUNNER_BUILD
#define RUNNER_DLL_EXPORT Q_DECL_EXPORT
#else
@@ -21,8 +23,9 @@ namespace mo2::python {
//
class IPythonRunner {
public:
virtual QList<QObject*> load(const QString& identifier) = 0;
virtual void unload(const QString& identifier) = 0;
virtual QList<QList<QObject*>>
load(std::filesystem::path const& pythonModule) = 0;
virtual void unload(std::filesystem::path const& pythonModule) = 0;
// initialize Python
//