diff --git a/src/mobase/wrappers/pyplugins.cpp b/src/mobase/wrappers/pyplugins.cpp index 86dd6a5..a5f872f 100644 --- a/src/mobase/wrappers/pyplugins.cpp +++ b/src/mobase/wrappers/pyplugins.cpp @@ -201,7 +201,7 @@ namespace mo2::python { .def("genFilePreview", &IPluginPreview::genFilePreview, "filename"_a, "max_size"_a); - py::class_>( m, "IPluginModPage", py::multiple_inheritance()) .def(py::init<>()) diff --git a/src/plugin_python_en.ts b/src/plugin_python_en.ts index 1d715ab..9bad015 100644 --- a/src/plugin_python_en.ts +++ b/src/plugin_python_en.ts @@ -4,70 +4,70 @@ ProxyPython - + Python Initialization failed - + 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 "no", and click the warning sign for further help.Afterwards you have to re-enable the python plugin. - + Python Proxy - + Proxy Plugin to allow plugins written in python to be loaded - + ModOrganizer path contains a semicolon - + Python DLL not found - + Invalid Python DLL - + Initializing Python failed - - + + invalid problem key %1 - + The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives, many softwares do not handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>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. - + The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem. - + 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. - + The initialization of the Python plugin DLL failed, unfortunately without any details. diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 3921653..4c43cc1 100644 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -48,8 +48,8 @@ install(FILES ${PYTHON_BUILD_PATH}/pythoncore/python${Python_VERSION_SHORT}.zip install(TARGETS mobase DESTINATION ${PYLIB_DIR}) # install PyQt6 -file(GLOB PYQT_DIR ${MO2_BUILD_PATH}/PyQt${QT_MAJOR_VERSION}*) -set(PYQT_LIB_DIR ${PYQT_DIR}/Lib/site-packages/PyQt${QT_MAJOR_VERSION}) +#file(GLOB PYQT_DIR ${MO2_BUILD_PATH}/PyQt${QT_MAJOR_VERSION}*) +set(PYQT_LIB_DIR ${PYTHON_ROOT}/Lib/site-packages/PyQt${QT_MAJOR_VERSION}) set(PYQT_TARGET_DIR ${PYLIB_DIR}/PyQt${QT_MAJOR_VERSION}) file(GLOB pyqt_files ${PYQT_LIB_DIR}/*.py ${PYQT_LIB_DIR}/*.pyd ${PYQT_LIB_DIR}/*.pyi) install(FILES ${pyqt_files} DESTINATION ${PYQT_TARGET_DIR}) diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 6ac78e1..c7c9747 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -129,9 +129,9 @@ bool ProxyPython::init(IOrganizer* moInfo) if (m_Runner) { const auto libpath = pluginFolder / "libs"; - const QStringList paths{ - QFileInfo(libpath / "pythoncore.zip").absoluteFilePath(), - QFileInfo(libpath).absoluteFilePath(), IOrganizer::getPluginDataPath()}; + const std::vector paths{ + libpath / "pythoncore.zip", libpath, + std::filesystem::path{IOrganizer::getPluginDataPath().toStdWString()}}; m_Runner->initialize(paths); } @@ -139,12 +139,15 @@ bool ProxyPython::init(IOrganizer* moInfo) m_MOInfo->setPersistent(name(), "tryInit", false); } + // reset DLL directory + SetDllDirectoryW(NULL); + if (!m_Runner || !m_Runner->isInitialized()) { m_LoadFailure = FailureType::INITIALIZATION; } - - // reset DLL directory - SetDllDirectoryW(NULL); + else { + m_Runner->addDllSearchPath(pluginFolder / "dlls"); + } return true; } diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index eac1e6b..8c190b9 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include #include @@ -12,6 +14,7 @@ #include "pybind11_qt/pybind11_qt.h" #include #include +#include #include #include @@ -36,7 +39,8 @@ namespace mo2::python { QList load(const QString& identifier) override; void unload(const QString& identifier) override; - bool initialize(QStringList const& paths) override; + bool initialize(std::vector const& pythonPaths) override; + void addDllSearchPath(std::filesystem::path const& dllPath) override; bool isInitialized() const override; private: @@ -57,7 +61,7 @@ namespace mo2::python { return std::make_unique(); } - bool PythonRunner::initialize(QStringList const& paths) + bool PythonRunner::initialize(std::vector const& pythonPaths) { // we only initialize Python once for the whole lifetime of the program, even if // MO2 is restarted and the proxy or PythonRunner objects are deleted and @@ -81,7 +85,11 @@ namespace mo2::python { // initialize the core Path of Python, this must be done before // initialization // - if (!paths.isEmpty()) { + if (!pythonPaths.empty()) { + QStringList paths; + for (auto const& p : pythonPaths) { + paths.append(QString::fromStdWString(absolute(p).native())); + } Py_SetPath(paths.join(';').toStdWString().c_str()); } @@ -123,6 +131,12 @@ namespace mo2::python { } } + void PythonRunner::addDllSearchPath(std::filesystem::path const& dllPath) + { + py::gil_scoped_acquire lock; + py::module_::import("os").attr("add_dll_directory")(absolute(dllPath)); + } + void PythonRunner::ensureFolderInPath(QString folder) { py::module_ sys = py::module_::import("sys"); diff --git a/src/runner/pythonrunner.h b/src/runner/pythonrunner.h index 78dbe95..5f9751b 100644 --- a/src/runner/pythonrunner.h +++ b/src/runner/pythonrunner.h @@ -1,6 +1,7 @@ #ifndef PYTHONRUNNER_H #define PYTHONRUNNER_H +#include #include #include @@ -25,14 +26,19 @@ namespace mo2::python { // initialize Python // - // paths contains the list of built-in paths for the Python library + // pythonPaths contains the list of built-in paths for the Python library // (pythonxxx.zip, etc.), an empty list uses the default Python paths (e.g., the // PYTHONPATH environment variable) // - virtual bool initialize(QStringList const& paths = {}) = 0; + virtual bool + initialize(std::vector const& pythonPaths = {}) = 0; - // check if the runner has been initialized, i.e., initialize() has been called - // and succeeded + // add a DLL search path + // + virtual void addDllSearchPath(std::filesystem::path const& dllPath) = 0; + + // check if the runner has been initialized, i.e., initialize() has been + // called and succeeded virtual bool isInitialized() const = 0; virtual ~IPythonRunner() {} diff --git a/tests/runner/CMakeLists.txt b/tests/runner/CMakeLists.txt index 42793d8..71a6843 100644 --- a/tests/runner/CMakeLists.txt +++ b/tests/runner/CMakeLists.txt @@ -19,7 +19,8 @@ target_link_libraries(runner-tests PUBLIC runner) set(PYLIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/pylibs) mo2_python_pip_install(runner-tests DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pylibs - PACKAGES + PACKAGES + pytest PyQt${QT_MAJOR_VERSION}==${QT_VERSION} PyQt${QT_MAJOR_VERSION}-Qt${QT_MAJOR_VERSION}==${QT_VERSION})