Commit bbaf7893 authored by Oswald Buddenhagen's avatar Oswald Buddenhagen
Browse files

"debug in terminal" feature.

includes complete refactoring of ConsoleProcess.
parent d35e1906
...@@ -6,4 +6,5 @@ SUBDIRS = \ ...@@ -6,4 +6,5 @@ SUBDIRS = \
aggregation \ aggregation \
extensionsystem \ extensionsystem \
utils \ utils \
utils/process_stub.pro \
cplusplus cplusplus
...@@ -37,9 +37,12 @@ ...@@ -37,9 +37,12 @@
#include <QtCore/QStringList> #include <QtCore/QStringList>
#include <QtCore/QProcess> #include <QtCore/QProcess>
#include <QtNetwork/QLocalServer>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
class QWinEventNotifier; class QWinEventNotifier;
class QTemporaryFile;
#endif #endif
namespace Core { namespace Core {
...@@ -50,41 +53,68 @@ class QWORKBENCH_UTILS_EXPORT ConsoleProcess : public QObject, public AbstractPr ...@@ -50,41 +53,68 @@ class QWORKBENCH_UTILS_EXPORT ConsoleProcess : public QObject, public AbstractPr
Q_OBJECT Q_OBJECT
public: public:
ConsoleProcess(QObject *parent); ConsoleProcess(QObject *parent = 0);
~ConsoleProcess(); ~ConsoleProcess();
bool start(const QString &program, const QStringList &args); bool start(const QString &program, const QStringList &args);
void stop(); void stop();
bool isRunning() const; void setDebug(bool on) { m_debug = on; }
qint64 applicationPID() const; bool isDebug() const { return m_debug; }
int exitCode() const;
bool isRunning() const; // This reflects the state of the console+stub
qint64 applicationPID() const { return m_appPid; }
int exitCode() const { return m_appCode; } // This will be the signal number if exitStatus == CrashExit
QProcess::ExitStatus exitStatus() const { return m_appStatus; }
#ifdef Q_OS_WIN
// These are public for WinGuiProcess. Should be in AbstractProcess, but it has no .cpp so far.
static QString createCommandline(const QString &program, const QStringList &args);
static QStringList fixEnvironment(const QStringList &env);
#endif
signals: signals:
void processError(const QString &error); void processError(const QString &error);
// These reflect the state of the actual client process
void processStarted(); void processStarted();
void processStopped(); void processStopped();
private: // These reflect the state of the console+stub
bool m_isRunning; void wrapperStarted();
void wrapperStopped();
#ifdef Q_OS_WIN
public:
static QString createCommandline(const QString &program,
const QStringList &args);
static QByteArray createEnvironment(const QStringList &env);
private slots: private slots:
void processDied(); void stubConnectionAvailable();
void readStubOutput();
void stubExited();
#ifdef Q_OS_WIN
void inferiorExited();
#endif
private: private:
QString stubServerListen();
void stubServerShutdown();
#ifdef Q_OS_WIN
void cleanupStub();
void cleanupInferior();
#endif
bool m_debug;
qint64 m_appPid;
int m_appCode;
QString m_executable;
QProcess::ExitStatus m_appStatus;
QLocalServer m_stubServer;
QLocalSocket *m_stubSocket;
#ifdef Q_OS_WIN
PROCESS_INFORMATION *m_pid; PROCESS_INFORMATION *m_pid;
HANDLE m_hInferior;
QWinEventNotifier *inferiorFinishedNotifier;
QWinEventNotifier *processFinishedNotifier; QWinEventNotifier *processFinishedNotifier;
QTemporaryFile *m_tempFile;
#else #else
private:
QProcess m_process; QProcess m_process;
private slots: QByteArray m_stubServerDir;
void processFinished(int, QProcess::ExitStatus);
#endif #endif
}; };
......
...@@ -29,57 +29,78 @@ ...@@ -29,57 +29,78 @@
#include "consoleprocess.h" #include "consoleprocess.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QTemporaryFile>
#include <QtNetwork/QLocalSocket>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
using namespace Core::Utils; using namespace Core::Utils;
ConsoleProcess::ConsoleProcess(QObject *parent) ConsoleProcess::ConsoleProcess(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
m_isRunning = false; m_debug = false;
} m_appPid = 0;
m_stubSocket = 0;
ConsoleProcess::~ConsoleProcess() connect(&m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable()));
{
m_process.setProcessChannelMode(QProcess::ForwardedChannels);
connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(stubExited()));
} }
static QString shellEscape(const QString &in) ConsoleProcess::~ConsoleProcess()
{ {
QString out = in; stop();
out.replace('\'', "'\''");
out.prepend('\'');
out.append('\'');
return out;
} }
bool ConsoleProcess::start(const QString &program, const QStringList &args) bool ConsoleProcess::start(const QString &program, const QStringList &args)
{ {
if (m_process.state() != QProcess::NotRunning) if (isRunning())
return false; return false;
QString shellArgs;
shellArgs += QLatin1String("cd ");
shellArgs += shellEscape(workingDirectory());
shellArgs += QLatin1Char(';');
shellArgs += shellEscape(program);
foreach (const QString &arg, args) {
shellArgs += QLatin1Char(' ');
shellArgs += shellEscape(arg);
}
shellArgs += QLatin1String("; echo; echo \"Press enter to close this window\"; read DUMMY");
m_process.setEnvironment(environment()); QString err = stubServerListen();
if (!err.isEmpty()) {
emit processError(tr("Cannot set up comm channel: %1").arg(err));
return false;
}
connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), QStringList xtermArgs;
this, SLOT(processFinished(int, QProcess::ExitStatus))); xtermArgs << "-e" << (QCoreApplication::applicationDirPath() + "/qtcreator_process_stub")
<< (m_debug ? "debug" : "exec")
<< m_stubServer.fullServerName()
<< tr("Press <RETURN> to close this window...")
<< workingDirectory() << environment() << ""
<< program << args;
m_process.start(QLatin1String("xterm"), QStringList() << QLatin1String("-e") << "/bin/sh" << "-c" << shellArgs); m_process.start(QLatin1String("xterm"), xtermArgs);
if (!m_process.waitForStarted()) if (!m_process.waitForStarted()) {
stubServerShutdown();
emit processError(tr("Cannot start console emulator xterm."));
return false; return false;
emit processStarted(); }
m_executable = program;
emit wrapperStarted();
return true; return true;
} }
void ConsoleProcess::processFinished(int, QProcess::ExitStatus) void ConsoleProcess::stop()
{ {
emit processStopped(); if (!isRunning())
return;
stubServerShutdown();
m_appPid = 0;
m_process.terminate();
if (!m_process.waitForFinished(1000))
m_process.kill();
m_process.waitForFinished();
} }
bool ConsoleProcess::isRunning() const bool ConsoleProcess::isRunning() const
...@@ -87,19 +108,97 @@ bool ConsoleProcess::isRunning() const ...@@ -87,19 +108,97 @@ bool ConsoleProcess::isRunning() const
return m_process.state() != QProcess::NotRunning; return m_process.state() != QProcess::NotRunning;
} }
void ConsoleProcess::stop() QString ConsoleProcess::stubServerListen()
{ {
m_process.terminate(); // We need to put the socket in a private directory, as some systems simply do not
m_process.waitForFinished(); // check the file permissions of sockets.
QString stubFifoDir;
forever {
{
QTemporaryFile tf;
if (!tf.open())
return tr("Cannot create temporary file: %2").arg(tf.errorString());
stubFifoDir = QFile::encodeName(tf.fileName());
}
// By now the temp file was deleted again
m_stubServerDir = QFile::encodeName(stubFifoDir);
if (!::mkdir(m_stubServerDir.constData(), 0700))
break;
if (errno != EEXIST)
return tr("Cannot create temporary directory %1: %2").arg(stubFifoDir, strerror(errno));
}
QString stubServer = stubFifoDir + "/stub-socket";
if (!m_stubServer.listen(stubServer)) {
::rmdir(m_stubServerDir.constData());
return tr("Cannot create socket %1: %2").arg(stubServer, m_stubServer.errorString());
}
return QString();
}
void ConsoleProcess::stubServerShutdown()
{
delete m_stubSocket;
m_stubSocket = 0;
if (m_stubServer.isListening()) {
m_stubServer.close();
::rmdir(m_stubServerDir.constData());
}
}
void ConsoleProcess::stubConnectionAvailable()
{
m_stubSocket = m_stubServer.nextPendingConnection();
connect(m_stubSocket, SIGNAL(readyRead()), SLOT(readStubOutput()));
} }
qint64 ConsoleProcess::applicationPID() const static QString errorMsg(int code)
{ {
return m_process.pid(); return QString::fromLocal8Bit(strerror(code));
} }
int ConsoleProcess::exitCode() const void ConsoleProcess::readStubOutput()
{ {
return m_process.exitCode(); while (m_stubSocket->canReadLine()) {
QByteArray out = m_stubSocket->readLine();
out.chop(1); // \n
if (out.startsWith("err:chdir ")) {
emit processError(tr("Cannot change to working directory %1: %2")
.arg(workingDirectory(), errorMsg(out.mid(10).toInt())));
} else if (out.startsWith("err:exec ")) {
emit processError(tr("Cannot execute %1: %2")
.arg(m_executable, errorMsg(out.mid(9).toInt())));
} else if (out.startsWith("pid ")) {
m_appPid = out.mid(4).toInt();
emit processStarted();
} else if (out.startsWith("exit ")) {
m_appStatus = QProcess::NormalExit;
m_appCode = out.mid(5).toInt();
m_appPid = 0;
emit processStopped();
} else if (out.startsWith("crash ")) {
m_appStatus = QProcess::CrashExit;
m_appCode = out.mid(6).toInt();
m_appPid = 0;
emit processStopped();
} else {
emit processError(tr("Unexpected output from helper program."));
m_process.terminate();
break;
}
}
} }
void ConsoleProcess::stubExited()
{
// The stub exit might get noticed before we read the error status.
if (m_stubSocket && m_stubSocket->state() == QLocalSocket::ConnectedState)
m_stubSocket->waitForDisconnected();
stubServerShutdown();
if (m_appPid) {
m_appStatus = QProcess::CrashExit;
m_appCode = -1;
m_appPid = 0;
emit processStopped(); // Maybe it actually did not, but keep state consistent
}
emit wrapperStopped();
}
...@@ -29,19 +29,31 @@ ...@@ -29,19 +29,31 @@
#include "consoleprocess.h" #include "consoleprocess.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/private/qwineventnotifier_p.h> #include <QtCore/QTemporaryFile>
#include <QtCore/QAbstractEventDispatcher> #include <QtCore/QAbstractEventDispatcher>
#include <QtCore/private/qwineventnotifier_p.h>
#include <QtNetwork/QLocalSocket>
#include <Tlhelp32.h> #include <stdlib.h>
using namespace Core::Utils; using namespace Core::Utils;
ConsoleProcess::ConsoleProcess(QObject *parent) ConsoleProcess::ConsoleProcess(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
m_isRunning = false; m_debug = false;
m_appPid = 0;
m_pid = 0; m_pid = 0;
m_hInferior = NULL;
m_tempFile = 0;
m_stubSocket = 0;
processFinishedNotifier = 0;
inferiorFinishedNotifier = 0;
connect(&m_stubServer, SIGNAL(newConnection()), SLOT(stubConnectionAvailable()));
} }
ConsoleProcess::~ConsoleProcess() ConsoleProcess::~ConsoleProcess()
...@@ -49,156 +61,234 @@ ConsoleProcess::~ConsoleProcess() ...@@ -49,156 +61,234 @@ ConsoleProcess::~ConsoleProcess()
stop(); stop();
} }
void ConsoleProcess::stop()
{
if (m_pid)
TerminateProcess(m_pid->hProcess, -1);
m_isRunning = false;
}
bool ConsoleProcess::start(const QString &program, const QStringList &args) bool ConsoleProcess::start(const QString &program, const QStringList &args)
{ {
if (m_isRunning) if (isRunning())
return false; return false;
QString err = stubServerListen();
if (!err.isEmpty()) {
emit processError(tr("Cannot set up comm channel: %1").arg(err));
return false;
}
if (!environment().isEmpty()) {
m_tempFile = new QTemporaryFile();
if (!m_tempFile->open()) {
stubServerShutdown();
emit processError(tr("Cannot create temp file: %1").arg(m_tempFile->errorString()));
delete m_tempFile;
m_tempFile = 0;
return false;
}
QTextStream out(m_tempFile);
out.setCodec("UTF-16LE");
out.setGenerateByteOrderMark(false);
foreach (const QString &var, fixEnvironment(environment()))
out << var << QChar(0);
out << QChar(0);
}
STARTUPINFO si; STARTUPINFO si;
ZeroMemory(&si, sizeof(si)); ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
if (m_pid) {
CloseHandle(m_pid->hThread);
CloseHandle(m_pid->hProcess);
delete m_pid;
m_pid = 0;
}
m_pid = new PROCESS_INFORMATION; m_pid = new PROCESS_INFORMATION;
ZeroMemory(m_pid, sizeof(PROCESS_INFORMATION)); ZeroMemory(m_pid, sizeof(PROCESS_INFORMATION));
QString cmdLine = QLatin1String("cmd /k ") QString workDir = QDir::toNativeSeparators(workingDirectory());
+ createCommandline(program, args) if (!workDir.isEmpty() && !workDir.endsWith('\\'))
+ QLatin1String(" & pause & exit"); workDir.append('\\');
QStringList stubArgs;
stubArgs << (m_debug ? "debug" : "exec")
<< m_stubServer.fullServerName()
<< workDir
<< (m_tempFile ? m_tempFile->fileName() : 0)
<< createCommandline(program, args)
<< tr("Press <RETURN> to close this window...");
QString cmdLine = createCommandline(
QCoreApplication::applicationDirPath() + "/qtcreator_process_stub.exe", stubArgs);
bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(),
0, 0, TRUE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, 0, 0, FALSE, CREATE_NEW_CONSOLE,
environment().isEmpty() ? 0 0, 0,
: createEnvironment(environment()).data(),
workingDirectory().isEmpty() ? 0
: (WCHAR*)QDir::convertSeparators(workingDirectory()).utf16(),
&si, m_pid); &si, m_pid);
if (!success) { if (!success) {
delete m_pid;
m_pid = 0;
delete m_tempFile;
m_tempFile = 0;
stubServerShutdown();
emit processError(tr("The process could not be started!")); emit processError(tr("The process could not be started!"));
return false; return false;
} }
if (QAbstractEventDispatcher::instance(thread())) { processFinishedNotifier = new QWinEventNotifier(m_pid->hProcess, this);
processFinishedNotifier = new QWinEventNotifier(m_pid->hProcess, this); connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), SLOT(stubExited()));
QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), this, SLOT(processDied())); emit wrapperStarted();
processFinishedNotifier->setEnabled(true); return true;
}
void ConsoleProcess::stop()
{
if (m_hInferior != NULL) {
TerminateProcess(m_hInferior, (unsigned)-1);
cleanupInferior();
}
if (m_pid) {
TerminateProcess(m_pid->hProcess, (unsigned)-1);
WaitForSingleObject(m_pid->hProcess, INFINITE);
cleanupStub();
} }
m_isRunning = true;
emit processStarted();
return success;
} }
bool ConsoleProcess::isRunning() const bool ConsoleProcess::isRunning() const
{ {
return m_isRunning; return m_pid != 0;
} }
void ConsoleProcess::processDied() QString ConsoleProcess::stubServerListen()
{ {
if (processFinishedNotifier) { if (m_stubServer.listen(QString::fromLatin1("creator-%1-%2")
processFinishedNotifier->setEnabled(false); .arg(QCoreApplication::applicationPid())
delete processFinishedNotifier; .arg(rand())))
processFinishedNotifier = 0; return QString();
} return m_stubServer.errorString();
delete m_pid;
m_pid = 0;
m_isRunning = false;
emit processStopped();
} }
qint64 ConsoleProcess::applicationPID() const void ConsoleProcess::stubServerShutdown()
{ {
if (m_pid) { delete m_stubSocket;
HANDLE hProcList = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); m_stubSocket = 0;
PROCESSENTRY32 procEntry; if (m_stubServer.isListening())
procEntry.dwSize = sizeof(PROCESSENTRY32); m_stubServer.close();
DWORD procId = 0; }
BOOL moreProc = Process32First(hProcList, &procEntry);
while (moreProc) {
if (procEntry.th32ParentProcessID == m_pid->dwProcessId) {
procId = procEntry.th32ProcessID;
break;
}
moreProc = Process32Next(hProcList, &procEntry);
}
CloseHandle(hProcList);