243 lines
8 KiB
C++
243 lines
8 KiB
C++
#include "pythonrunner.h"
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QStandardPaths>
|
|
#include <QCoreApplication>
|
|
|
|
PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent)
|
|
: QObject(parent), m_outputWidget(outputWidget), m_process(nullptr), m_venvProcess(nullptr),
|
|
ready(false), venvPreparing(false) {
|
|
m_process = new QProcess(this);
|
|
m_venvProcess = new QProcess(this);
|
|
connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable);
|
|
connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable);
|
|
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onProcessFinished);
|
|
connect(m_venvProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
|
QByteArray output = m_venvProcess->readAllStandardOutput();
|
|
m_outputWidget->append(output);
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
emit venvPreparationProgress(output);
|
|
});
|
|
connect(m_venvProcess, &QProcess::readyReadStandardError, this, [this]() {
|
|
QByteArray error = m_venvProcess->readAllStandardError();
|
|
m_outputWidget->append(error);
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
emit venvPreparationProgress(error);
|
|
});
|
|
connect(m_venvProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PythonRunner::onVenvProcessFinished);
|
|
}
|
|
|
|
PythonRunner::~PythonRunner() {
|
|
if (m_process) {
|
|
m_process->terminate();
|
|
m_process->waitForFinished(1000);
|
|
}
|
|
if (m_venvProcess) {
|
|
m_venvProcess->terminate();
|
|
m_venvProcess->waitForFinished(1000);
|
|
}
|
|
}
|
|
|
|
bool PythonRunner::runScript(const QString& scriptContent) {
|
|
if (m_process->state() == QProcess::Running) {
|
|
m_process->terminate();
|
|
m_process->waitForFinished(1000);
|
|
}
|
|
|
|
m_outputWidget->clear();
|
|
|
|
// Check if venv is ready, if not prepare it
|
|
if (!isVenvReady()) {
|
|
m_outputWidget->append("Preparing Python environment...\n");
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
prepareVenv();
|
|
return true; // Return early - venv preparation will handle script execution when ready
|
|
}
|
|
|
|
QString pythonPath = getVenvPythonPath();
|
|
m_process->start(pythonPath, QStringList() << "-u" << "-c" << scriptContent);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PythonRunner::stopScript() {
|
|
if (m_process && m_process->state() == QProcess::Running) {
|
|
m_process->terminate();
|
|
}
|
|
}
|
|
|
|
void PythonRunner::onOutputAvailable() {
|
|
QByteArray output = m_process->readAllStandardOutput();
|
|
m_outputWidget->append(output);
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
}
|
|
|
|
void PythonRunner::onErrorAvailable() {
|
|
QByteArray error = m_process->readAllStandardError();
|
|
m_outputWidget->append(error);
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
}
|
|
|
|
void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
|
if (exitStatus == QProcess::NormalExit && exitCode != 0) {
|
|
m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode));
|
|
} else if (exitStatus == QProcess::CrashExit) {
|
|
m_outputWidget->append("Python was stopped\n");
|
|
}
|
|
emit scriptFinished(exitCode);
|
|
}
|
|
|
|
void PythonRunner::onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
|
if (exitStatus == QProcess::NormalExit && exitCode == 0) {
|
|
// Check if this was venv creation or pip installation
|
|
QString venvPythonPath = getVenvPythonPath();
|
|
if (!QFile::exists(venvPythonPath)) {
|
|
// This was venv creation, now install eismultiplexer package
|
|
installEismultiplexerPackage();
|
|
} else {
|
|
// This was pip installation, mark as ready
|
|
ready = true;
|
|
m_outputWidget->append("Python environment prepared successfully.\n");
|
|
|
|
// Signal that venv preparation is complete
|
|
{
|
|
QMutexLocker locker(&venvMutex);
|
|
venvPreparing = false;
|
|
venvCondition.wakeAll();
|
|
}
|
|
|
|
emit venvPreparationFinished();
|
|
}
|
|
} else {
|
|
ready = false;
|
|
m_outputWidget->append(QString("Failed to prepare Python environment (code %1)\n").arg(exitCode));
|
|
|
|
// Signal that venv preparation failed
|
|
{
|
|
QMutexLocker locker(&venvMutex);
|
|
venvPreparing = false;
|
|
venvCondition.wakeAll();
|
|
}
|
|
|
|
emit venvPreparationFinished();
|
|
}
|
|
}
|
|
|
|
void PythonRunner::prepareVenv() {
|
|
// Start venv preparation in a non-blocking manner
|
|
QMutexLocker locker(&venvMutex);
|
|
venvPreparing = true;
|
|
|
|
// Create virtual environment
|
|
QString pythonPath = getPythonExecutable();
|
|
QString venvPath = getVenvPath();
|
|
|
|
m_outputWidget->append(QString("Creating virtual environment at %1...\n").arg(venvPath));
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
|
|
QStringList args;
|
|
args << "-m" << "venv" << venvPath;
|
|
|
|
m_venvProcess->start(pythonPath, args);
|
|
if (!m_venvProcess->waitForStarted()) {
|
|
m_outputWidget->append("Failed to start virtual environment creation process.\n");
|
|
ready = false;
|
|
venvPreparing = false;
|
|
return;
|
|
}
|
|
|
|
// Don't wait for completion - this will be handled by the finished signal
|
|
// The installEismultiplexerPackage will be called in the finished signal handler
|
|
}
|
|
|
|
void PythonRunner::installEismultiplexerPackage() {
|
|
QString venvPythonPath = getVenvPythonPath();
|
|
QString venvPipPath = QString("%1 -m pip").arg(venvPythonPath);
|
|
|
|
m_outputWidget->append("Installing eismultiplexer package...\n");
|
|
m_outputWidget->moveCursor(QTextCursor::End);
|
|
|
|
QStringList args;
|
|
args << "install" << "eismultiplexer";
|
|
|
|
m_venvProcess->start(venvPipPath, args);
|
|
if (!m_venvProcess->waitForStarted()) {
|
|
m_outputWidget->append("Failed to start pip installation process.\n");
|
|
ready = false;
|
|
|
|
// Signal that venv preparation failed
|
|
{
|
|
QMutexLocker locker(&venvMutex);
|
|
venvPreparing = false;
|
|
venvCondition.wakeAll();
|
|
}
|
|
|
|
emit venvPreparationFinished();
|
|
return;
|
|
}
|
|
|
|
// Don't wait for completion - this will be handled by the finished signal
|
|
}
|
|
|
|
QString PythonRunner::getPythonExecutable() {
|
|
QString pythonPath;
|
|
|
|
// Try to find python3 first, then python
|
|
QStringList pythonCandidates;
|
|
pythonCandidates << "python3" << "python";
|
|
|
|
for (const QString& candidate : pythonCandidates) {
|
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
|
QString path = env.value("PATH");
|
|
QStringList pathDirs = path.split(';', Qt::SkipEmptyParts);
|
|
|
|
for (const QString& dir : pathDirs) {
|
|
QString pythonExecutable = QDir(dir).filePath(candidate);
|
|
if (QFile::exists(pythonExecutable)) {
|
|
pythonPath = pythonExecutable;
|
|
break;
|
|
}
|
|
}
|
|
if (!pythonPath.isEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If still not found, use the default python from QProcessEnvironment
|
|
if (pythonPath.isEmpty()) {
|
|
pythonPath = "python";
|
|
}
|
|
|
|
return pythonPath;
|
|
}
|
|
|
|
QString PythonRunner::getVenvPath() {
|
|
// Create venv in a temporary directory
|
|
QString venvDir = dir.path() + QDir::separator() + "venv";
|
|
return venvDir;
|
|
}
|
|
|
|
QString PythonRunner::getVenvPythonPath() {
|
|
QString venvPath = getVenvPath();
|
|
QString venvPython;
|
|
|
|
// Determine the correct python path for the venv based on platform
|
|
QString pythonExecutable = "python";
|
|
|
|
// On Windows, the python executable is in Scripts directory
|
|
// On Linux/macOS, it's in bin directory
|
|
QString venvBinDir;
|
|
#ifdef Q_OS_WIN
|
|
venvBinDir = venvPath + QDir::separator() + "Scripts";
|
|
#else
|
|
venvBinDir = venvPath + QDir::separator() + "bin";
|
|
#endif
|
|
|
|
venvPython = QDir(venvBinDir).filePath(pythonExecutable);
|
|
return venvPython;
|
|
}
|
|
|
|
bool PythonRunner::isVenvReady() {
|
|
QString venvPythonPath = getVenvPythonPath();
|
|
return QFile::exists(venvPythonPath);
|
|
}
|