diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-13 06:26:18 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-13 06:26:18 -0500 |
| commit | de4db79e5158017c002cc91c14fa9e58420e7774 (patch) | |
| tree | 74f8781aef49afa4e1cc3ba94c5a08876cadab0f /src | |
| parent | 84a43a2e6afbdfab92c610d47aa75e3876ffacd6 (diff) | |
Bundle PBS Python 3.12 + PyQt6, restore Python plugins as .py files
- Replace portable Python approach with python-build-standalone 3.12.13
(20260310 release) bundled directly in the distribution. Python is now
always available without user setup — removes the Python settings tab.
- PyQt6 staged to plugins/libs/PyQt6/ with bundled Qt stripped out;
patchelf'd to use our existing bundled Qt in lib/ instead of duplicating it.
- Restored native-converted plugins back to .py files:
Form43Checker, ScriptExtenderPluginChecker, DDSPreview, basic_games,
rootbuilder, installer_omod. Removed all *_native CMake targets.
- pythonrunner.cpp: removed venv/MO2_PYTHON_DIR lookup; always uses
bundled Python at <exe_dir>/python, system Python as last-resort fallback.
- plugincontainer.cpp: removed fluorine/python_enabled gate (Python always
loads), demoted proxy loading log messages from warn to debug, silenced
[plugin-diag] stderr spam.
- Dockerfile: switched from uv venv to PBS install_only tarball; PBS Python
used for both build-time pybind11 compilation and runtime distribution.
- build-inner.sh: aggressive Python staging (strip test/tkinter/ensurepip/
distutils/lib2to3/idlelib; wipe build-time site-packages, restore psutil+vdf).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/src/plugincontainer.cpp | 23 | ||||
| -rw-r--r-- | src/src/settingsdialog.cpp | 3 | ||||
| -rw-r--r-- | src/src/settingsdialog.ui | 161 | ||||
| -rw-r--r-- | src/src/settingsdialogpython.cpp | 206 | ||||
| -rw-r--r-- | src/src/settingsdialogpython.h | 29 |
5 files changed, 5 insertions, 417 deletions
diff --git a/src/src/plugincontainer.cpp b/src/src/plugincontainer.cpp index a170c99..aa11f6c 100644 --- a/src/src/plugincontainer.cpp +++ b/src/src/plugincontainer.cpp @@ -63,10 +63,8 @@ static void ensureBundledPluginsLinked(const QString& bundledDir, }
#endif
-static void printPluginDiagToStderr(const QString& message)
+static void printPluginDiagToStderr(const QString&)
{
- std::fprintf(stderr, "[plugin-diag] %s\n", qUtf8Printable(message));
- std::fflush(stderr);
}
// Welcome to the wonderful world of MO2 plugin management!
@@ -654,7 +652,7 @@ IPlugin* PluginContainer::registerPlugin(QObject* plugin, const QString& filepat const QString pluginRoot =
m_PluginPath.isEmpty() ? AppConfig::pluginsPath() : m_PluginPath;
QStringList filepaths = proxy->pluginList(pluginRoot);
- log::warn("proxy '{}' discovered {} proxied plugin candidate(s) in '{}'",
+ log::debug("proxy '{}' discovered {} proxied plugin candidate(s) in '{}'",
proxy->name(), filepaths.size(),
QDir::toNativeSeparators(pluginRoot));
printPluginDiagToStderr(
@@ -859,7 +857,7 @@ std::vector<QObject*> PluginContainer::loadProxied(const QString& filepath, std::vector<QObject*> proxiedPlugins;
try {
- log::warn("loading proxied plugin candidate '{}' via proxy '{}'",
+ log::debug("loading proxied plugin candidate '{}' via proxy '{}'",
QDir::toNativeSeparators(filepath),
(proxy ? proxy->name() : QStringLiteral("<null>")));
printPluginDiagToStderr(
@@ -871,7 +869,7 @@ std::vector<QObject*> PluginContainer::loadProxied(const QString& filepath, // per file and do not have a good way of supporting multiple inheritance.
QList<QObject*> matchingPlugins = proxy->load(filepath);
if (matchingPlugins.isEmpty()) {
- log::warn("no plugins were returned for proxied candidate '{}' via proxy '{}'",
+ log::debug("no plugins were returned for proxied candidate '{}' via proxy '{}'",
QDir::toNativeSeparators(filepath), proxy->name());
printPluginDiagToStderr(
QString("no plugins were returned for proxied candidate '%1' via proxy '%2'")
@@ -930,7 +928,7 @@ std::vector<QObject*> PluginContainer::loadProxied(const QString& filepath, }
}
}
- log::warn("finished proxied candidate '{}' via proxy '{}': {} plugin(s) loaded",
+ log::debug("finished proxied candidate '{}' via proxy '{}': {} plugin(s) loaded",
QDir::toNativeSeparators(filepath), proxy->name(),
proxiedPlugins.size());
printPluginDiagToStderr(
@@ -1305,17 +1303,6 @@ void PluginContainer::loadPlugins() }
}
- // Skip the Python proxy plugin unless the user has enabled Python support
- // in Settings > Python. Native C++ replacements handle all default plugins.
- if (iter.fileName() == "libplugin_python.so" ||
- iter.fileName() == "plugin_python.dll") {
- if (!QSettings().value("fluorine/python_enabled", false).toBool()) {
- log::debug("plugin \"{}\" skipped (Python plugins disabled in settings)",
- iter.fileName());
- continue;
- }
- }
-
if (loadCheck.isOpen()) {
loadCheck.write(iter.fileName().toUtf8());
loadCheck.write("\n");
diff --git a/src/src/settingsdialog.cpp b/src/src/settingsdialog.cpp index 757c6f8..7a4c2b9 100644 --- a/src/src/settingsdialog.cpp +++ b/src/src/settingsdialog.cpp @@ -25,7 +25,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "settingsdialogpaths.h"
#include "settingsdialogplugins.h"
#include "settingsdialogproton.h"
-#include "settingsdialogpython.h"
#include "settingsdialogtheme.h"
#include "settingsdialogworkarounds.h"
#include "ui_settingsdialog.h"
@@ -53,8 +52,6 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti m_tabs.push_back(
std::unique_ptr<SettingsTab>(new ProtonSettingsTab(settings, *this)));
m_tabs.push_back(
- std::unique_ptr<SettingsTab>(new PythonSettingsTab(settings, *this)));
- m_tabs.push_back(
std::unique_ptr<SettingsTab>(new WorkaroundsSettingsTab(settings, *this)));
}
diff --git a/src/src/settingsdialog.ui b/src/src/settingsdialog.ui index 2089c06..646b9b6 100644 --- a/src/src/settingsdialog.ui +++ b/src/src/settingsdialog.ui @@ -1897,167 +1897,6 @@ If you disable this feature, MO will only display official DLCs this way. Please </item> </layout> </widget> - <widget class="QWidget" name="pythonTab"> - <attribute name="title"> - <string>Python</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_python"> - <item> - <widget class="QGroupBox" name="groupBox_python"> - <property name="title"> - <string>Python Plugin Support</string> - </property> - <layout class="QGridLayout" name="gridLayout_python"> - <item row="0" column="0"> - <widget class="QLabel" name="pythonEnableLabel"> - <property name="text"> - <string>Enable Python Plugins:</string> - </property> - </widget> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QCheckBox" name="pythonEnableCheck"> - <property name="text"> - <string>Load .py plugins (requires Python 3 and PyQt6)</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="pythonPathLabel"> - <property name="text"> - <string>Python Path:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="pythonPathEdit"> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="placeholderText"> - <string>Auto-detected</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QPushButton" name="pythonRefreshButton"> - <property name="text"> - <string>Detect</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="pythonVersionLabel"> - <property name="text"> - <string>Version:</string> - </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QLabel" name="pythonVersionValue"> - <property name="text"> - <string>Not detected</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_venv"> - <property name="title"> - <string>Virtual Environment</string> - </property> - <layout class="QGridLayout" name="gridLayout_venv"> - <item row="0" column="0"> - <widget class="QLabel" name="venvPathLabel"> - <property name="text"> - <string>Venv Location:</string> - </property> - </widget> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QLineEdit" name="venvPathEdit"> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="venvStatusLabel"> - <property name="text"> - <string>Status:</string> - </property> - </widget> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QLabel" name="venvStatusValue"> - <property name="text"> - <string>Not created</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QPushButton" name="createVenvButton"> - <property name="text"> - <string>Create Virtual Environment</string> - </property> - <property name="toolTip"> - <string>Creates a Python virtual environment and installs PyQt6. Required for loading .py plugins.</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QPushButton" name="deleteVenvButton"> - <property name="text"> - <string>Delete Venv</string> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QPushButton" name="openVenvFolderButton"> - <property name="text"> - <string>Open Folder</string> - </property> - </widget> - </item> - <item row="3" column="0" colspan="3"> - <widget class="QTextEdit" name="pythonLogOutput"> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="visible"> - <bool>false</bool> - </property> - <property name="maximumHeight"> - <number>150</number> - </property> - <property name="font"> - <font> - <family>monospace</family> - <pointsize>9</pointsize> - </font> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer_python"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> <widget class="QWidget" name="workaroundTab"> <attribute name="title"> <string>Workarounds</string> diff --git a/src/src/settingsdialogpython.cpp b/src/src/settingsdialogpython.cpp deleted file mode 100644 index ebad6eb..0000000 --- a/src/src/settingsdialogpython.cpp +++ /dev/null @@ -1,206 +0,0 @@ -#include "settingsdialogpython.h" - -#include "fluorinepaths.h" -#include "ui_settingsdialog.h" - -#include <log.h> -#include <uibase/utility.h> - -#include <QCheckBox> -#include <QDir> -#include <QFileInfo> -#include <QMessageBox> -#include <QProcess> -#include <QPushButton> -#include <QSettings> -#include <QStandardPaths> - -PythonSettingsTab::PythonSettingsTab(Settings& s, SettingsDialog& d) - : QObject(&d), SettingsTab(s, d) -{ - m_venvPath = fluorineDataDir() + "/python-venv"; - ui->venvPathEdit->setText(m_venvPath); - - // Load saved state - bool pythonEnabled = QSettings().value("fluorine/python_enabled", false).toBool(); - ui->pythonEnableCheck->setChecked(pythonEnabled); - - detectPython(); - refreshVenvState(); - - QObject::connect(ui->pythonRefreshButton, &QPushButton::clicked, this, - &PythonSettingsTab::detectPython); - QObject::connect(ui->createVenvButton, &QPushButton::clicked, this, - &PythonSettingsTab::onCreateVenv); - QObject::connect(ui->deleteVenvButton, &QPushButton::clicked, this, - &PythonSettingsTab::onDeleteVenv); - QObject::connect(ui->openVenvFolderButton, &QPushButton::clicked, this, - &PythonSettingsTab::onOpenVenvFolder); -} - -void PythonSettingsTab::update() -{ - QSettings().setValue("fluorine/python_enabled", - ui->pythonEnableCheck->isChecked()); -} - -void PythonSettingsTab::detectPython() -{ - m_pythonPath = QStandardPaths::findExecutable("python3"); - if (m_pythonPath.isEmpty()) { - m_pythonPath = QStandardPaths::findExecutable("python"); - } - - if (m_pythonPath.isEmpty()) { - ui->pythonPathEdit->setText(""); - ui->pythonVersionValue->setText("Python 3 not found in PATH"); - return; - } - - ui->pythonPathEdit->setText(m_pythonPath); - - // Get version - QProcess proc; - proc.start(m_pythonPath, {"--version"}); - proc.waitForFinished(5000); - - if (proc.exitCode() == 0) { - QString version = QString::fromUtf8(proc.readAllStandardOutput()).trimmed(); - if (version.isEmpty()) { - version = QString::fromUtf8(proc.readAllStandardError()).trimmed(); - } - ui->pythonVersionValue->setText(version); - } else { - ui->pythonVersionValue->setText("Error detecting version"); - } -} - -void PythonSettingsTab::refreshVenvState() -{ - QDir venvDir(m_venvPath); - bool exists = venvDir.exists() && venvDir.exists("bin/python3"); - - if (exists) { - // Check if PyQt6 is installed - QProcess proc; - proc.start(m_venvPath + "/bin/python3", - {"-c", "import PyQt6; print(PyQt6.__file__)"}); - proc.waitForFinished(5000); - - if (proc.exitCode() == 0) { - ui->venvStatusValue->setText("Active (PyQt6 installed)"); - } else { - ui->venvStatusValue->setText("Active (PyQt6 NOT installed)"); - } - } else { - ui->venvStatusValue->setText("Not created"); - } - - ui->deleteVenvButton->setEnabled(exists); - ui->openVenvFolderButton->setEnabled(exists); -} - -void PythonSettingsTab::onCreateVenv() -{ - if (m_pythonPath.isEmpty()) { - QMessageBox::warning(parentWidget(), tr("No Python"), - tr("Python 3 was not found in PATH.\n\n" - "Install Python 3 using your package manager:\n" - " Arch: pacman -S python\n" - " Ubuntu: apt install python3 python3-venv\n" - " Fedora: dnf install python3")); - return; - } - - ui->pythonLogOutput->setVisible(true); - ui->pythonLogOutput->clear(); - ui->venvStatusValue->setText("Creating virtual environment..."); - - // Create venv - { - QProcess proc; - proc.start(m_pythonPath, {"-m", "venv", m_venvPath}); - proc.waitForFinished(30000); - - QString output = QString::fromUtf8(proc.readAllStandardOutput()); - QString errors = QString::fromUtf8(proc.readAllStandardError()); - if (!output.isEmpty()) - ui->pythonLogOutput->append(output); - if (!errors.isEmpty()) - ui->pythonLogOutput->append(errors); - - if (proc.exitCode() != 0) { - ui->venvStatusValue->setText("Failed to create venv"); - ui->pythonLogOutput->append("venv creation failed with exit code " + - QString::number(proc.exitCode())); - return; - } - } - - ui->pythonLogOutput->append("Virtual environment created. Installing PyQt6..."); - ui->venvStatusValue->setText("Installing PyQt6..."); - - // Install PyQt6 - { - QProcess proc; - proc.start(m_venvPath + "/bin/pip", - {"install", "--upgrade", "pip", "PyQt6"}); - proc.waitForFinished(120000); // PyQt6 is large, give it time - - QString output = QString::fromUtf8(proc.readAllStandardOutput()); - QString errors = QString::fromUtf8(proc.readAllStandardError()); - if (!output.isEmpty()) - ui->pythonLogOutput->append(output); - if (!errors.isEmpty()) - ui->pythonLogOutput->append(errors); - - if (proc.exitCode() != 0) { - ui->venvStatusValue->setText("PyQt6 installation failed"); - ui->pythonLogOutput->append("pip install failed with exit code " + - QString::number(proc.exitCode())); - refreshVenvState(); - return; - } - } - - ui->pythonLogOutput->append("PyQt6 installed successfully."); - MOBase::log::info("Python venv created at {}", m_venvPath); - - refreshVenvState(); -} - -void PythonSettingsTab::onDeleteVenv() -{ - QDir venvDir(m_venvPath); - if (!venvDir.exists()) { - refreshVenvState(); - return; - } - - auto result = QMessageBox::question( - parentWidget(), tr("Delete Virtual Environment"), - tr("Delete the Python virtual environment at:\n%1\n\n" - "This will disable .py plugin loading until recreated.") - .arg(m_venvPath)); - - if (result != QMessageBox::Yes) - return; - - if (venvDir.removeRecursively()) { - ui->pythonLogOutput->setVisible(false); - MOBase::log::info("Python venv deleted at {}", m_venvPath); - } else { - QMessageBox::warning(parentWidget(), tr("Error"), - tr("Failed to delete the virtual environment.")); - } - - refreshVenvState(); -} - -void PythonSettingsTab::onOpenVenvFolder() -{ - QDir venvDir(m_venvPath); - if (venvDir.exists()) { - MOBase::shell::Explore(venvDir); - } -} diff --git a/src/src/settingsdialogpython.h b/src/src/settingsdialogpython.h deleted file mode 100644 index 187d04b..0000000 --- a/src/src/settingsdialogpython.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SETTINGSDIALOGPYTHON_H -#define SETTINGSDIALOGPYTHON_H - -#include <QObject> - -#include "settings.h" -#include "settingsdialog.h" - -class PythonSettingsTab : public QObject, public SettingsTab -{ - Q_OBJECT - -public: - PythonSettingsTab(Settings& settings, SettingsDialog& dialog); - - void update() override; - -private: - void detectPython(); - void refreshVenvState(); - void onCreateVenv(); - void onDeleteVenv(); - void onOpenVenvFolder(); - - QString m_pythonPath; - QString m_venvPath; -}; - -#endif // SETTINGSDIALOGPYTHON_H |
