From b5bd510f5905c1675b6e85270c51580050b63972 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Tue, 14 Oct 2025 13:52:55 +0200 Subject: [PATCH] Run user script in venv and install eismultiplexer --- mainwindow.cpp | 18 +++++++ mainwindow.h | 1 + pythonrunner.cpp | 122 ++++++++++++++++++++++++++++++++++++++++++++++- pythonrunner.h | 17 ++++++- 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index b7bd50e..da5fb34 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -53,6 +53,12 @@ MainWindow::MainWindow(QWidget *parent): connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); + connect(&pythonRunner, &PythonRunner::venvSetupProgress, this, &MainWindow::onVenvSetupProgress); + + // Disable run button until venv is ready + ui->pushButtonRun->setEnabled(false); + ui->statusbar->showMessage("Setting up Python environment..."); + enumerateDevices(); generateExample(); } @@ -290,6 +296,18 @@ void MainWindow::disconnectDevices() multiplexers.clear(); } +void MainWindow::onVenvSetupProgress(const QString& message) { + ui->statusbar->showMessage(message); + if (message.contains("completed successfully", Qt::CaseInsensitive) || + message.contains("already exists", Qt::CaseInsensitive)) { + ui->pushButtonRun->setEnabled(true); + ui->statusbar->showMessage("Ready"); + } else if (message.contains("failed", Qt::CaseInsensitive)) { + ui->pushButtonRun->setEnabled(false); + ui->statusbar->showMessage("Error: Python environment setup failed. Please check the output."); + } +} + void MainWindow::generateExample() { QString example = diff --git a/mainwindow.h b/mainwindow.h index 686c6a2..460b735 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -44,6 +44,7 @@ private slots: void runScript(); void stopScript(); void readState(); + void onVenvSetupProgress(const QString& message); private: void enumerateDevices(); diff --git a/pythonrunner.cpp b/pythonrunner.cpp index e2ba0ab..cfbc8ff 100644 --- a/pythonrunner.cpp +++ b/pythonrunner.cpp @@ -1,12 +1,18 @@ #include "pythonrunner.h" #include +#include +#include +#include PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) - : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { + : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr), m_venvProcess(nullptr), venvReady(false) { m_process = new QProcess(this); connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable); connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable); connect(m_process, QOverload::of(&QProcess::finished), this, &PythonRunner::onProcessFinished); + + // Start venv setup in background + QThread::create([this]() { setupVenv(); }); } PythonRunner::~PythonRunner() { @@ -14,6 +20,103 @@ PythonRunner::~PythonRunner() { m_process->terminate(); m_process->waitForFinished(1000); } + if (m_venvProcess) { + m_venvProcess->terminate(); + m_venvProcess->waitForFinished(1000); + } +} + +QString PythonRunner::venvPath() { + return dir.path() + QDir::separator() + "venv"; +} + +QString PythonRunner::pythonExePath() { + QString venv = venvPath(); +#ifdef Q_OS_WIN + return venv + "\\Scripts\\python.exe"; +#else + return venv + "/bin/python"; +#endif +} + +void PythonRunner::ensureVenvReady() { + QMutexLocker locker(&venvMutex); + if (!venvReady) { + venvCondition.wait(&venvMutex); + } +} + +void PythonRunner::setupVenv() { + QString venv = venvPath(); + QString pythonCmd = "python3"; +#ifdef Q_OS_WIN + pythonCmd = "python"; +#endif + + // Check if venv already exists + QDir venvDir(venv); + if (venvDir.exists()) { + venvReady = true; + emit venvSetupProgress("Virtual environment already exists"); + return; + } + + // Create virtual environment + emit venvSetupProgress("Creating virtual environment..."); + + m_venvProcess = new QProcess(); + connect(m_venvProcess, QOverload::of(&QProcess::finished), this, &PythonRunner::onVenvProcessFinished); + + QStringList args; +#ifdef Q_OS_WIN + args << "-m" << "venv" << "venv"; +#else + args << "-m" << "venv" << venv; +#endif + + m_venvProcess->setWorkingDirectory(dir.path()); + m_venvProcess->start(pythonCmd, args); + if (!m_venvProcess->waitForStarted()) { + emit venvSetupProgress("Failed to start venv creation process"); + venvReady = false; + m_venvProcess->deleteLater(); + m_venvProcess = nullptr; + } +} + +void PythonRunner::onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + // Install eismultiplexer package + emit venvSetupProgress("Installing eismultiplexer package..."); + + QProcess installProcess; + installProcess.setWorkingDirectory(dir.path()); + QStringList installArgs; + installArgs << "-m" << "pip" << "install" << "eismultiplexer"; + + installProcess.start(pythonExePath(), installArgs); + if (!installProcess.waitForFinished(30000)) { // 30 second timeout + emit venvSetupProgress("Package installation timed out"); + venvReady = false; + } else { + QByteArray output = installProcess.readAllStandardOutput(); + QByteArray error = installProcess.readAllStandardError(); + if (!error.isEmpty()) { + emit venvSetupProgress("Installation error: " + QString::fromLocal8Bit(error)); + venvReady = false; + } else { + emit venvSetupProgress("Package installed successfully"); + venvReady = true; + } + } + } else { + emit venvSetupProgress(QString("Virtual environment creation failed with code %1").arg(exitCode)); + venvReady = false; + } + + // Clean up + m_venvProcess->deleteLater(); + m_venvProcess = nullptr; } bool PythonRunner::runScript(const QString& scriptContent) { @@ -23,7 +126,18 @@ bool PythonRunner::runScript(const QString& scriptContent) { } m_outputWidget->clear(); - m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent); + + // Check if venv is ready + if (!venvReady) { + m_outputWidget->append("Error: Virtual environment not ready. Please wait for the setup to complete.\n"); + emit scriptFinished(-1); + return false; + } + + m_outputWidget->append("Running script in virtual environment...\n"); + + QString pythonPath = pythonExePath(); + m_process->start(pythonPath, QStringList() << "-u" << "-c" << scriptContent); return true; } @@ -54,3 +168,7 @@ void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStat } emit scriptFinished(exitCode); } + +void PythonRunner::onVenvSetupFinished() { + // This slot can be used to handle venv setup completion if needed +} diff --git a/pythonrunner.h b/pythonrunner.h index 8a8ac15..904c359 100644 --- a/pythonrunner.h +++ b/pythonrunner.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include class PythonRunner : public QObject { Q_OBJECT @@ -19,13 +22,25 @@ private slots: void onOutputAvailable(); void onErrorAvailable(); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onVenvSetupFinished(); + void setupVenv(); + +public slots: + void onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); signals: void scriptFinished(int code); + void venvSetupProgress(const QString& message); private: QTextEdit* m_outputWidget; QProcess* m_process; - bool ready; + QProcess* m_venvProcess; QTemporaryDir dir; + QString venvPath(); + QString pythonExePath(); + void ensureVenvReady(); + bool venvReady; + QMutex venvMutex; + QWaitCondition venvCondition; };