use venv
This commit is contained in:
parent
4c2a4790c0
commit
e47c806a2a
4 changed files with 286 additions and 9 deletions
|
|
@ -15,7 +15,8 @@ MainWindow::MainWindow(QWidget *parent):
|
|||
pythonOutput(this),
|
||||
pythonRunner(&pythonOutput),
|
||||
currentFilePath(""),
|
||||
isFileModified(false)
|
||||
isFileModified(false),
|
||||
uiBlocked(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
|
|
@ -49,10 +50,19 @@ MainWindow::MainWindow(QWidget *parent):
|
|||
updateStatus();
|
||||
});
|
||||
|
||||
// Connect venv preparation signals
|
||||
connect(&pythonRunner, &PythonRunner::venvPreparationProgress, &pythonOutput, [this](const QString& message) {
|
||||
pythonOutput.append(message);
|
||||
pythonOutput.moveCursor(QTextCursor::End);
|
||||
});
|
||||
connect(&pythonRunner, &PythonRunner::venvPreparationFinished, this, &MainWindow::onVenvPreparationFinished);
|
||||
|
||||
// Connect Run and Stop buttons
|
||||
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::venvPreparationProgress, this, &MainWindow::onVenvPreparationProgress);
|
||||
|
||||
enumerateDevices();
|
||||
generateExample();
|
||||
}
|
||||
|
|
@ -264,12 +274,42 @@ void MainWindow::enumerateDevices()
|
|||
|
||||
void MainWindow::runScript() {
|
||||
QString scriptContent = codeEditor.toPlainText();
|
||||
pythonRunner.runScript(scriptContent);
|
||||
ui->pushButtonRun->setEnabled(false);
|
||||
ui->pushButtonStop->setEnabled(true);
|
||||
codeEditor.setEnabled(false);
|
||||
ui->scrollArea->setEnabled(false);
|
||||
disconnectDevices();
|
||||
|
||||
// Check if we need to prepare the venv
|
||||
if (!pythonRunner.isVenvReady()) {
|
||||
// Block UI for venv preparation
|
||||
blockUIForVenvPreparation();
|
||||
pythonRunner.runScript(scriptContent);
|
||||
} else {
|
||||
// Normal script execution flow
|
||||
pythonRunner.runScript(scriptContent);
|
||||
ui->pushButtonRun->setEnabled(false);
|
||||
ui->pushButtonStop->setEnabled(true);
|
||||
codeEditor.setEnabled(false);
|
||||
ui->scrollArea->setEnabled(false);
|
||||
disconnectDevices();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onVenvPreparationFinished() {
|
||||
// Unblock UI when venv preparation is complete
|
||||
unblockUI();
|
||||
|
||||
// If venv is ready, enable the run button and run the script if needed
|
||||
if (pythonRunner.isVenvReady()) {
|
||||
ui->pushButtonRun->setEnabled(true);
|
||||
|
||||
// If we have a script to run (from the first runScript call), run it now
|
||||
if (uiBlocked) {
|
||||
QString scriptContent = codeEditor.toPlainText();
|
||||
pythonRunner.runScript(scriptContent);
|
||||
ui->pushButtonRun->setEnabled(false);
|
||||
ui->pushButtonStop->setEnabled(true);
|
||||
codeEditor.setEnabled(false);
|
||||
ui->scrollArea->setEnabled(false);
|
||||
disconnectDevices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::stopScript() {
|
||||
|
|
@ -283,6 +323,32 @@ void MainWindow::stopScript() {
|
|||
enumerateDevices();
|
||||
}
|
||||
|
||||
void MainWindow::onVenvPreparationProgress(const QString& message) {
|
||||
// Update the output widget with progress
|
||||
pythonOutput.append(message);
|
||||
pythonOutput.moveCursor(QTextCursor::End);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::blockUIForVenvPreparation() {
|
||||
|
||||
uiBlocked = true;
|
||||
ui->pushButtonRun->setEnabled(false);
|
||||
ui->pushButtonStop->setEnabled(false);
|
||||
codeEditor.setEnabled(false);
|
||||
ui->scrollArea->setEnabled(false);
|
||||
pythonOutput.append("Preparing Python environment. Please wait...\n");
|
||||
pythonOutput.moveCursor(QTextCursor::End);
|
||||
}
|
||||
|
||||
void MainWindow::unblockUI() {
|
||||
if (uiBlocked) {
|
||||
uiBlocked = false;
|
||||
// UI will be re-enabled when script execution starts
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::disconnectDevices()
|
||||
{
|
||||
for(auto& multiplexer : multiplexers)
|
||||
|
|
|
|||
|
|
@ -44,14 +44,19 @@ private slots:
|
|||
void runScript();
|
||||
void stopScript();
|
||||
void readState();
|
||||
void onVenvPreparationProgress(const QString& message);
|
||||
void onVenvPreparationFinished();
|
||||
|
||||
private:
|
||||
void enumerateDevices();
|
||||
void generateExample();
|
||||
void updateStatus();
|
||||
void disconnectDevices();
|
||||
void blockUIForVenvPreparation();
|
||||
void unblockUI();
|
||||
QString currentFilePath;
|
||||
bool isFileModified;
|
||||
bool uiBlocked;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
|
|||
191
pythonrunner.cpp
191
pythonrunner.cpp
|
|
@ -1,12 +1,30 @@
|
|||
#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) {
|
||||
: 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() {
|
||||
|
|
@ -14,6 +32,10 @@ PythonRunner::~PythonRunner() {
|
|||
m_process->terminate();
|
||||
m_process->waitForFinished(1000);
|
||||
}
|
||||
if (m_venvProcess) {
|
||||
m_venvProcess->terminate();
|
||||
m_venvProcess->waitForFinished(1000);
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonRunner::runScript(const QString& scriptContent) {
|
||||
|
|
@ -23,7 +45,17 @@ bool PythonRunner::runScript(const QString& scriptContent) {
|
|||
}
|
||||
|
||||
m_outputWidget->clear();
|
||||
m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -54,3 +86,158 @@ void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStat
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
#include <QProcess>
|
||||
#include <QObject>
|
||||
#include <QTemporaryDir>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
class PythonRunner : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
@ -19,13 +22,29 @@ private slots:
|
|||
void onOutputAvailable();
|
||||
void onErrorAvailable();
|
||||
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
|
||||
signals:
|
||||
void scriptFinished(int code);
|
||||
void venvPreparationProgress(const QString& message);
|
||||
void venvPreparationFinished();
|
||||
|
||||
private:
|
||||
void prepareVenv();
|
||||
void installEismultiplexerPackage();
|
||||
QString getPythonExecutable();
|
||||
QString getVenvPath();
|
||||
QString getVenvPythonPath();
|
||||
|
||||
public:
|
||||
bool isVenvReady();
|
||||
|
||||
QTextEdit* m_outputWidget;
|
||||
QProcess* m_process;
|
||||
QProcess* m_venvProcess;
|
||||
bool ready;
|
||||
QTemporaryDir dir;
|
||||
QMutex venvMutex;
|
||||
QWaitCondition venvCondition;
|
||||
bool venvPreparing;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue