aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-19 17:25:48 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-19 17:25:48 -0600
commit5467e210668eb24de9a4429ef0ddcbc6442e8932 (patch)
tree6c672183779c800963d73db5bee6742bf08c65fd /libs
parent6d70b7fa6a9cdf8ea59c7aa98f92249c5a4e0060 (diff)
Add Python init diagnostics, AppImage build system, misc Linux fixes
- python: switch Py_InitializeEx → Py_InitializeFromConfig for explicit error reporting; add dladdr + fprintf diagnostics to trace which DSO Python symbols resolve to and whether Py_IsInitialized succeeds - python: log RTLD_NOLOAD vs fresh-load outcome when promoting libpython to RTLD_GLOBAL - build: add Docker-based AppImage build script (build.sh), update Dockerfile and build-inner.sh for AppImage-only workflow - build: remove Flatpak support entirely; move AppImage metadata (desktop/metainfo/icon) from flatpak/ to data/ - Add Monster Hunter Wilds basic_games plugin - .gitignore: exclude squashfs-root, build-source, flatpak-repo, __pycache__, fomod-plus-settings.ini - Various Linux port fixes across wineprefix, processrunner, proton launcher, FUSE connector, instance manager, download manager, sanitychecks, and NaK Rust paths/runtime_wrap Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs')
-rw-r--r--libs/basic_games/games/baldursgate3/pak_parser.py21
-rw-r--r--libs/basic_games/games/game_monsterhunterwilds.py15
-rw-r--r--libs/game_bethesda/src/*.cpp0
-rw-r--r--libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp4
-rw-r--r--libs/game_bethesda/src/gamebryo/gamegamebryo.cpp6
-rw-r--r--libs/game_gamebryo/src/gamebryo/gamebryolocalsavegames.cpp4
-rw-r--r--libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp2
-rw-r--r--libs/installer_fomod_plus/installer/lib/Logger.h2
-rw-r--r--libs/installer_fomod_plus/installer/ui/UIHelper.cpp1
-rw-r--r--libs/installer_fomod_plus/patchwizard/lib/PatchFinder.cpp1
-rw-r--r--libs/installer_fomod_plus/scanner/archiveparser.h1
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/FomodDB.h2
-rw-r--r--libs/nak/src/paths.rs6
-rw-r--r--libs/nak/src/runtime_wrap.rs36
-rw-r--r--libs/plugin_python/src/proxy/proxypython.cpp7
-rw-r--r--libs/plugin_python/src/runner/pythonrunner.cpp178
16 files changed, 164 insertions, 122 deletions
diff --git a/libs/basic_games/games/baldursgate3/pak_parser.py b/libs/basic_games/games/baldursgate3/pak_parser.py
index 4ebc83c..533c72e 100644
--- a/libs/basic_games/games/baldursgate3/pak_parser.py
+++ b/libs/basic_games/games/baldursgate3/pak_parser.py
@@ -281,21 +281,12 @@ class BG3PakParser:
except Exception:
pass
- if os.path.exists("/.flatpak-info"):
- # In Flatpak, host wine binaries can't execute directly
- # (missing host libraries). Use flatpak-spawn to run on host.
- spawn_cmd = ["flatpak-spawn", "--host"]
- if wine_prefix:
- spawn_cmd.append(f"--env=WINEPREFIX={wine_prefix}")
- spawn_cmd.extend(["bash", "-c", command])
- result = subprocess.run(spawn_cmd, **kwargs)
- else:
- env = os.environ.copy()
- if wine_prefix:
- env["WINEPREFIX"] = wine_prefix
- kwargs["env"] = env
- kwargs["shell"] = True
- result = subprocess.run(command, **kwargs)
+ env = os.environ.copy()
+ if wine_prefix:
+ env["WINEPREFIX"] = wine_prefix
+ kwargs["env"] = env
+ kwargs["shell"] = True
+ result = subprocess.run(command, **kwargs)
if result.returncode:
qWarning(
diff --git a/libs/basic_games/games/game_monsterhunterwilds.py b/libs/basic_games/games/game_monsterhunterwilds.py
new file mode 100644
index 0000000..419377c
--- /dev/null
+++ b/libs/basic_games/games/game_monsterhunterwilds.py
@@ -0,0 +1,15 @@
+from ..basic_game import BasicGame
+
+
+class MonsterHunterWildsGame(BasicGame):
+ Name = "Monster Hunter: Wilds Support Plugin"
+ Author = "AbyssDragnonModding"
+ Version = "1.0.0"
+
+ GameName = "Monster Hunter: Wilds"
+ GameShortName = "monsterhunterwilds"
+ GameBinary = "MonsterHunterWilds.exe"
+ GameDataPath = "%GAME_PATH%"
+ GameSaveExtension = "bin"
+ GameNexusId = 6993
+ GameSteamId = 2246340
diff --git a/libs/game_bethesda/src/*.cpp b/libs/game_bethesda/src/*.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/game_bethesda/src/*.cpp
diff --git a/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp b/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp
index e4df0ea..4c1199c 100644
--- a/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp
+++ b/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp
@@ -56,7 +56,9 @@ QString GamebryoLocalSavegames::localSavesDummy() const
QDir GamebryoLocalSavegames::localSavesDirectory() const
{
- return QDir(m_Game->myGamesPath()).absoluteFilePath(localSavesDummy());
+ QString dummy = localSavesDummy();
+ dummy.replace("\\", "/");
+ return QDir(m_Game->myGamesPath()).absoluteFilePath(dummy);
}
QDir GamebryoLocalSavegames::localGameDirectory() const
diff --git a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
index b53d823..4a92440 100644
--- a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
+++ b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
@@ -494,11 +494,7 @@ QString GameGamebryo::myGamesPath() const
return findInRegistry(HKEY_LOCAL_MACHINE, L"Software\\LOOT", L"Installed Path") +
"/Loot.exe";
#else
- // On Linux, look for loot in common locations
- QString flatpakPath = QDir::homePath() + "/.var/app/io.github.loot.loot/data/LOOT/Loot";
- if (QFileInfo(flatpakPath).exists())
- return flatpakPath;
- // Try system PATH
+ // On Linux, look for loot in PATH
QString systemLoot = QStandardPaths::findExecutable("loot");
if (!systemLoot.isEmpty())
return systemLoot;
diff --git a/libs/game_gamebryo/src/gamebryo/gamebryolocalsavegames.cpp b/libs/game_gamebryo/src/gamebryo/gamebryolocalsavegames.cpp
index cf78eba..d0cc2ef 100644
--- a/libs/game_gamebryo/src/gamebryo/gamebryolocalsavegames.cpp
+++ b/libs/game_gamebryo/src/gamebryo/gamebryolocalsavegames.cpp
@@ -44,7 +44,9 @@ QString GamebryoLocalSavegames::localSavesDummy() const
QDir GamebryoLocalSavegames::localSavesDirectory() const
{
- return QDir(m_Game->myGamesPath()).absoluteFilePath(localSavesDummy());
+ QString dummy = localSavesDummy();
+ dummy.replace("\\", "/");
+ return QDir(m_Game->myGamesPath()).absoluteFilePath(dummy);
}
QDir GamebryoLocalSavegames::localGameDirectory() const
diff --git a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp b/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
index 690bdbb..c748495 100644
--- a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
+++ b/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
@@ -34,8 +34,6 @@ bool FomodPlusInstaller::init(IOrganizer* organizer)
mOrganizer = organizer;
mFomodContent = make_shared<FomodDataContent>(organizer);
log.setLogFilePath(QDir::currentPath().toStdString() + "/logs/fomodplus.log");
- std::cout << "QDir::currentPath(): " << QDir::currentPath().toStdString() << std::endl;
- std::cout << "mOrganizer->basePath() : " << mOrganizer->basePath().toStdString() << std::endl;
// REMEMBER: This mFomodDB persists beyond the scope of an individual install. Do not do anything nasty to it.
mFomodDb = std::make_unique<FomodDB>(mOrganizer->basePath().toStdString());
diff --git a/libs/installer_fomod_plus/installer/lib/Logger.h b/libs/installer_fomod_plus/installer/lib/Logger.h
index fdeb1f0..53fb7d9 100644
--- a/libs/installer_fomod_plus/installer/lib/Logger.h
+++ b/libs/installer_fomod_plus/installer/lib/Logger.h
@@ -90,8 +90,6 @@ public:
if (mLogFile.is_open()) {
writeLog(mLogFile);
}
-
- writeLog(std::cout);
}
Logger& operator=(const Logger&) = delete;
diff --git a/libs/installer_fomod_plus/installer/ui/UIHelper.cpp b/libs/installer_fomod_plus/installer/ui/UIHelper.cpp
index 56aff85..5f34e5d 100644
--- a/libs/installer_fomod_plus/installer/ui/UIHelper.cpp
+++ b/libs/installer_fomod_plus/installer/ui/UIHelper.cpp
@@ -27,7 +27,6 @@ bool CtrlClickEventFilter::eventFilter(QObject* obj, QEvent* event)
if (mouseEvent->button() == Qt::LeftButton &&
mouseEvent->modifiers() & Qt::ControlModifier) {
// TODO: Add Ctrl+click handler logic here
- std::cout << "Ctrl+click detected on plugin: " << mPlugin->getName() << " in group: " << mGroup->getName() << std::endl;
// For now, just fall through to default behavior
}
}
diff --git a/libs/installer_fomod_plus/patchwizard/lib/PatchFinder.cpp b/libs/installer_fomod_plus/patchwizard/lib/PatchFinder.cpp
index cd06b3a..855fe2b 100644
--- a/libs/installer_fomod_plus/patchwizard/lib/PatchFinder.cpp
+++ b/libs/installer_fomod_plus/patchwizard/lib/PatchFinder.cpp
@@ -77,7 +77,6 @@ void PatchFinder::populateInstalledPlugins()
const auto mod_tree = mod->fileTree();
for (auto it = mod_tree->begin(); it != mod_tree->end(); ++it) {
if ((*it)->isFile() && isPluginFile((*it)->name())) {
- std::cout << "Plugin: " << (*it)->name().toStdString() << std::endl;
m_installedPlugins[mod].emplace_back((*it)->name().toStdString());
m_installedPluginsCacheSet.insert((*it)->name().toStdString());
}
diff --git a/libs/installer_fomod_plus/scanner/archiveparser.h b/libs/installer_fomod_plus/scanner/archiveparser.h
index 4058d43..8819a84 100644
--- a/libs/installer_fomod_plus/scanner/archiveparser.h
+++ b/libs/installer_fomod_plus/scanner/archiveparser.h
@@ -83,7 +83,6 @@ public:
}
if (hasFomodFiles(archive->getFileList())) {
- std::cout << "Found FOMOD files in " << qualifiedInstallerPath.toStdString() << std::endl;
return ScanResult::HAS_FOMOD;
}
return ScanResult::NO_FOMOD;
diff --git a/libs/installer_fomod_plus/share/FOMODData/FomodDB.h b/libs/installer_fomod_plus/share/FOMODData/FomodDB.h
index d1a9aa2..f5ea6f5 100644
--- a/libs/installer_fomod_plus/share/FOMODData/FomodDB.h
+++ b/libs/installer_fomod_plus/share/FOMODData/FomodDB.h
@@ -33,8 +33,6 @@ public:
for (const auto &group: installStep.optionalFileGroups.groups) {
for (const auto &plugin: group.plugins.plugins) {
// Create a DB entry for the given plugin if it has an ESP
- std::cout << "\nPlugin: " << plugin.name << std::endl;
-
for (auto file: plugin.files.files) {
if (file.isFolder || !isPluginFile(file.source)) {
continue;
diff --git a/libs/nak/src/paths.rs b/libs/nak/src/paths.rs
index e86c284..f6dde6c 100644
--- a/libs/nak/src/paths.rs
+++ b/libs/nak/src/paths.rs
@@ -1,11 +1,11 @@
//! Shared data directory for Fluorine Manager.
//!
-//! All data lives under `~/.var/app/com.fluorine.manager/`.
+//! All data lives under `~/.local/share/fluorine/`.
use std::path::PathBuf;
-/// Returns the Fluorine data directory (`~/.var/app/com.fluorine.manager`).
+/// Returns the Fluorine data directory (`~/.local/share/fluorine`).
pub fn data_dir() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
- PathBuf::from(home).join(".var/app/com.fluorine.manager")
+ PathBuf::from(home).join(".local/share/fluorine")
}
diff --git a/libs/nak/src/runtime_wrap.rs b/libs/nak/src/runtime_wrap.rs
index d33bd59..9b5ff89 100644
--- a/libs/nak/src/runtime_wrap.rs
+++ b/libs/nak/src/runtime_wrap.rs
@@ -34,23 +34,6 @@ fn find_in_path(binary: &str) -> Option<PathBuf> {
}
pub fn resolve_umu_run() -> Option<PathBuf> {
- if is_flatpak() {
- // In Flatpak, umu-run must run on the host via flatpak-spawn --host.
- let host_copy = env::var("XDG_DATA_HOME")
- .ok()
- .map(PathBuf::from)
- .unwrap_or_else(|| {
- let home = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
- PathBuf::from(home).join(".local/share")
- })
- .join("fluorine/umu-run");
- if host_copy.exists() {
- return Some(host_copy);
- }
- // Fall back to bare name for host PATH resolution.
- return Some(PathBuf::from("umu-run"));
- }
-
let bundled = env::var("NAK_BUNDLED_UMU_RUN")
.ok()
.map(PathBuf::from)
@@ -64,14 +47,8 @@ pub fn resolve_umu_run() -> Option<PathBuf> {
}
}
-pub fn is_flatpak() -> bool {
- Path::new("/.flatpak-info").exists()
-}
-
/// Build a command to run `exe` with the given environment variables.
///
-/// In Flatpak mode, `flatpak-spawn --host` is used and env vars are passed as
-/// `--env=KEY=VALUE` flags (the host process does NOT inherit the sandbox env).
/// In steam-run mode, the command is wrapped with `steam-run`.
/// Otherwise the command runs directly.
pub fn build_command<S: AsRef<OsStr>>(
@@ -86,19 +63,6 @@ pub fn build_command<S: AsRef<OsStr>>(
}
return cmd;
}
- if is_flatpak() {
- let mut cmd = Command::new("flatpak-spawn");
- cmd.arg("--host");
- for (key, value) in envs {
- cmd.arg(format!(
- "--env={}={}",
- key,
- value.as_ref().to_string_lossy()
- ));
- }
- cmd.arg(exe.as_ref());
- return cmd;
- }
let mut cmd = Command::new(exe);
for (key, value) in envs {
cmd.env(key, value.as_ref());
diff --git a/libs/plugin_python/src/proxy/proxypython.cpp b/libs/plugin_python/src/proxy/proxypython.cpp
index 196bf23..c9c7ba2 100644
--- a/libs/plugin_python/src/proxy/proxypython.cpp
+++ b/libs/plugin_python/src/proxy/proxypython.cpp
@@ -224,6 +224,9 @@ bool ProxyPython::init(IOrganizer* moInfo)
if (!m_Runner || !m_Runner->isInitialized()) {
m_LoadFailure = FailureType::INITIALIZATION;
+ MOBase::log::error("Python proxy: interpreter failed to initialize, "
+ "Python plugins will be unavailable");
+ return true; // return true so the plugin stays loaded (shows diagnostic)
}
else {
m_Runner->addDllSearchPath(pluginDataRoot / "dlls");
@@ -296,7 +299,7 @@ QStringList ProxyPython::pluginList(const QDir& pluginPath) const
QList<QObject*> ProxyPython::load(const QString& identifier)
{
- if (!m_Runner) {
+ if (!m_Runner || !m_Runner->isInitialized()) {
return {};
}
return m_Runner->load(identifier);
@@ -304,7 +307,7 @@ QList<QObject*> ProxyPython::load(const QString& identifier)
void ProxyPython::unload(const QString& identifier)
{
- if (m_Runner) {
+ if (m_Runner && m_Runner->isInitialized()) {
return m_Runner->unload(identifier);
}
}
diff --git a/libs/plugin_python/src/runner/pythonrunner.cpp b/libs/plugin_python/src/runner/pythonrunner.cpp
index 05a0f30..b0bf48c 100644
--- a/libs/plugin_python/src/runner/pythonrunner.cpp
+++ b/libs/plugin_python/src/runner/pythonrunner.cpp
@@ -85,21 +85,6 @@ namespace mo2::python {
return true;
}
- std::optional<QByteArray> oldPythonHome;
- std::optional<QByteArray> oldPythonPath;
- auto restorePythonEnv = [&]() {
- if (oldPythonHome.has_value()) {
- setenv("PYTHONHOME", oldPythonHome->constData(), 1);
- } else {
- unsetenv("PYTHONHOME");
- }
- if (oldPythonPath.has_value()) {
- setenv("PYTHONPATH", oldPythonPath->constData(), 1);
- } else {
- unsetenv("PYTHONPATH");
- }
- };
-
try {
static const char* argv0 = "ModOrganizer.exe";
@@ -107,14 +92,49 @@ namespace mo2::python {
#ifdef MO2_PYTHON_SHARED_LIBRARY
// Ensure libpython symbols are globally visible for extension modules
// loaded later (_struct, PyQt6, etc.).
+ bool usedNoload = true;
void* pyHandle =
dlopen(MO2_PYTHON_SHARED_LIBRARY, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
if (pyHandle == nullptr) {
+ usedNoload = false;
pyHandle = dlopen(MO2_PYTHON_SHARED_LIBRARY, RTLD_NOW | RTLD_GLOBAL);
}
if (pyHandle == nullptr) {
- MOBase::log::warn("failed to dlopen python shared library '{}': {}",
+ MOBase::log::warn("python: failed to dlopen '{}': {}",
MO2_PYTHON_SHARED_LIBRARY, dlerror());
+ } else {
+ MOBase::log::debug("python: '{}' promoted to RTLD_GLOBAL ({})",
+ MO2_PYTHON_SHARED_LIBRARY,
+ usedNoload ? "RTLD_NOLOAD" : "fresh load");
+ }
+
+ // Diagnose which DSO the Python init symbols resolve to globally.
+ {
+ Dl_info di;
+ void* sym = dlsym(RTLD_DEFAULT, "Py_InitializeEx");
+ if (sym && dladdr(sym, &di)) {
+ fprintf(stderr, "[py-diag] Py_InitializeEx global from '%s'\n",
+ di.dli_fname);
+ MOBase::log::debug("python: Py_InitializeEx (global) from '{}'",
+ di.dli_fname);
+ } else {
+ fprintf(stderr, "[py-diag] Py_InitializeEx NOT in global scope "
+ "(sym=%p, err=%s)\n",
+ sym, dlerror());
+ MOBase::log::warn("python: Py_InitializeEx NOT in global scope "
+ "(sym={}, err={})",
+ sym, dlerror());
+ }
+ sym = dlsym(RTLD_DEFAULT, "Py_IsInitialized");
+ if (sym && dladdr(sym, &di)) {
+ fprintf(stderr, "[py-diag] Py_IsInitialized global from '%s'\n",
+ di.dli_fname);
+ MOBase::log::debug("python: Py_IsInitialized (global) from '{}'",
+ di.dli_fname);
+ } else {
+ fprintf(stderr, "[py-diag] Py_IsInitialized NOT in global scope\n");
+ MOBase::log::warn("python: Py_IsInitialized NOT in global scope");
+ }
}
#endif
#endif
@@ -130,6 +150,25 @@ namespace mo2::python {
} else {
pythonHome = QCoreApplication::applicationDirPath() + "/python";
}
+ if (!QDir(pythonHome).exists()) {
+ MOBase::log::warn("python: PYTHONHOME dir '{}' does not exist",
+ pythonHome);
+ }
+
+ std::optional<QByteArray> oldPythonHome;
+ std::optional<QByteArray> oldPythonPath;
+ auto restorePythonEnv = [&]() {
+ if (oldPythonHome.has_value()) {
+ setenv("PYTHONHOME", oldPythonHome->constData(), 1);
+ } else {
+ unsetenv("PYTHONHOME");
+ }
+ if (oldPythonPath.has_value()) {
+ setenv("PYTHONPATH", oldPythonPath->constData(), 1);
+ } else {
+ unsetenv("PYTHONPATH");
+ }
+ };
if (const char* v = std::getenv("PYTHONHOME"); v != nullptr) {
oldPythonHome = QByteArray(v);
}
@@ -137,48 +176,86 @@ namespace mo2::python {
oldPythonPath = QByteArray(v);
}
- if (QDir(pythonHome).exists()) {
- setenv("PYTHONHOME", pythonHome.toUtf8().constData(), 1);
-
- const QDir libDir(pythonHome + "/lib");
- const auto pyDirs =
- libDir.entryList({"python3.*"}, QDir::Dirs | QDir::NoDotAndDotDot);
- if (!pyDirs.isEmpty()) {
- const QString pyver = pyDirs.first();
- const QString pyPath = QString("%1/lib/%2:%1/lib/%2/site-packages:%1")
- .arg(pythonHome, pyver);
- setenv("PYTHONPATH", pyPath.toUtf8().constData(), 1);
- }
- }
-
// Paths we want to prepend/append for MO2 plugin loading.
auto paths = pythonPaths;
- PyConfig config;
- PyConfig_InitPythonConfig(&config);
+ // Configure bundled Python like running python/bin/python3 directly.
+ const QDir libDir(pythonHome + "/lib");
+ const auto pyDirs =
+ libDir.entryList({"python3.*"}, QDir::Dirs | QDir::NoDotAndDotDot);
+ const QString pyverDir = pyDirs.isEmpty() ? QStringLiteral("python3.13")
+ : pyDirs.first();
+ const QString stdlibDir = pythonHome + "/lib/" + pyverDir;
+ const QString stdlibZip = pythonHome + "/lib/python313.zip";
+ const QString rootDynloadDir = pythonHome + "/lib-dynload";
+ const QString rootPlatDir = pythonHome + "/plat-linux2";
+ const QString dynloadDir = stdlibDir + "/lib-dynload";
+ const QString siteDir = stdlibDir + "/site-packages";
- // from PyBind11
- config.parse_argv = 0;
- config.install_signal_handlers = 0;
+ QStringList corePaths = {stdlibDir, siteDir, dynloadDir};
+ if (QFile::exists(stdlibZip)) {
+ corePaths.prepend(stdlibZip);
+ }
+ if (QDir(rootDynloadDir).exists()) {
+ corePaths.append(rootDynloadDir);
+ }
+ if (QDir(rootPlatDir).exists()) {
+ corePaths.append(rootPlatDir);
+ }
- // from MO2
- config.site_import = 1;
- config.optimization_level = 2;
+ corePaths.append(pythonHome);
+ const QByteArray pyPath = corePaths.join(":").toUtf8();
+ setenv("PYTHONHOME", pythonHome.toUtf8().constData(), 1);
+ setenv("PYTHONPATH", pyPath.constData(), 1);
- py::initialize_interpreter(&config, 1, &argv0, true);
+ fprintf(stderr,
+ "[py-diag] calling Py_InitializeFromConfig, PYTHONHOME='%s', "
+ "Py_IsInitialized before=%d\n",
+ pythonHome.toUtf8().constData(), Py_IsInitialized());
+ MOBase::log::debug(
+ "python: calling Py_InitializeFromConfig, PYTHONHOME='{}', "
+ "Py_IsInitialized before={}",
+ pythonHome, Py_IsInitialized());
- // Restore process environment after interpreter startup so
- // subprocesses (umu/NaK/tools) are not forced onto MO2's Python.
- restorePythonEnv();
+ // Use Py_InitializeFromConfig (Python 3.8+) for explicit error reporting.
+ {
+ PyConfig config;
+ PyConfig_InitPythonConfig(&config);
+ // PYTHONHOME is already set via setenv; PyConfig reads it from env.
+ PyStatus status = Py_InitializeFromConfig(&config);
+ PyConfig_Clear(&config);
+ if (PyStatus_Exception(status)) {
+ fprintf(stderr,
+ "[py-diag] Py_InitializeFromConfig FAILED: '%s' [in '%s']\n",
+ status.err_msg ? status.err_msg : "(no message)",
+ status.func ? status.func : "(no func)");
+ MOBase::log::error(
+ "python: Py_InitializeFromConfig failed: '{}' [in '{}']",
+ status.err_msg ? status.err_msg : "(no message)",
+ status.func ? status.func : "(no func)");
+ restorePythonEnv();
+ return false;
+ }
+ }
+
+ fprintf(stderr, "[py-diag] Py_IsInitialized after=%d\n",
+ Py_IsInitialized());
+ MOBase::log::debug("python: Py_IsInitialized after={}",
+ Py_IsInitialized());
if (!Py_IsInitialized()) {
+ const char* ph = std::getenv("PYTHONHOME");
+ const char* pp = std::getenv("PYTHONPATH");
MOBase::log::error(
- "failed to init python: failed to initialize interpreter.");
-
- if (PyGILState_Check()) {
- PyEval_SaveThread();
- }
-
+ "failed to init python: Py_IsInitialized() returned false.");
+ MOBase::log::error(" PYTHONHOME='{}', PYTHONPATH='{}'",
+ ph ? ph : "(null)", pp ? pp : "(null)");
+ MOBase::log::error(" pythonHome='{}', exists={}", pythonHome,
+ QDir(pythonHome).exists() ? "yes" : "no");
+ const QString encPath = pythonHome + "/lib/python3.13/encodings";
+ MOBase::log::error(" encodings dir='{}', exists={}", encPath,
+ QDir(encPath).exists() ? "yes" : "no");
+ restorePythonEnv();
return false;
}
@@ -201,11 +278,11 @@ namespace mo2::python {
// 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();
+ restorePythonEnv();
return true;
}
catch (const py::error_already_set& ex) {
- restorePythonEnv();
MOBase::log::error("failed to init python: {}", ex.what());
return false;
}
@@ -353,7 +430,8 @@ 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 {}: {}", identifier,
+ ex.what());
throw pyexcept::PythonError(ex);
}
}