215 lines
6.1 KiB
C++
215 lines
6.1 KiB
C++
// Copyright (C) 2017 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include "qqmldebugprocess_p.h"
|
|
|
|
#include <QtCore/qdebug.h>
|
|
#include <QtCore/qfileinfo.h>
|
|
#include <QtCore/qdir.h>
|
|
#include <QtCore/qregularexpression.h>
|
|
|
|
#include <QtTest/qtest.h>
|
|
|
|
QQmlDebugProcess::QQmlDebugProcess(const QString &executable, QObject *parent)
|
|
: QObject(parent)
|
|
, m_executable(executable)
|
|
{
|
|
m_process.setProcessChannelMode(QProcess::MergedChannels);
|
|
connect(&m_process, &QProcess::readyReadStandardOutput,
|
|
this, &QQmlDebugProcess::processAppOutput);
|
|
connect(&m_process, &QProcess::errorOccurred,
|
|
this, &QQmlDebugProcess::processError);
|
|
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
this, [this]() {
|
|
if (m_state == SessionUnknown)
|
|
m_state = SessionFailed;
|
|
emit finished();
|
|
});
|
|
}
|
|
|
|
QQmlDebugProcess::~QQmlDebugProcess()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
QString QQmlDebugProcess::stateString() const
|
|
{
|
|
QString stateStr;
|
|
switch (m_process.state()) {
|
|
case QProcess::NotRunning: {
|
|
stateStr = "not running";
|
|
if (m_process.exitStatus() == QProcess::CrashExit)
|
|
stateStr += " (crashed!)";
|
|
else
|
|
stateStr += ", return value " + QString::number(m_process.exitCode());
|
|
break;
|
|
}
|
|
case QProcess::Starting:
|
|
stateStr = "starting";
|
|
break;
|
|
case QProcess::Running:
|
|
stateStr = "running";
|
|
break;
|
|
}
|
|
return stateStr;
|
|
}
|
|
|
|
void QQmlDebugProcess::start(const QStringList &arguments)
|
|
{
|
|
#ifdef Q_OS_MACOS
|
|
// make sure m_executable points to the actual binary even if it's inside an app bundle
|
|
QFileInfo binFile(m_executable);
|
|
if (!binFile.isExecutable()) {
|
|
QDir bundleDir(m_executable + ".app");
|
|
if (bundleDir.exists()) {
|
|
m_executable = bundleDir.absoluteFilePath("Contents/MacOS/" + binFile.baseName());
|
|
//qDebug() << Q_FUNC_INFO << "found bundled binary" << m_executable;
|
|
}
|
|
}
|
|
#endif
|
|
m_port = 0;
|
|
m_process.setEnvironment(QProcess::systemEnvironment() + m_environment);
|
|
m_process.start(m_executable, arguments);
|
|
if (!m_process.waitForStarted()) {
|
|
qWarning() << "QML Debug Client: Could not launch app " << m_executable
|
|
<< ": " << m_process.errorString();
|
|
}
|
|
}
|
|
|
|
void QQmlDebugProcess::stop()
|
|
{
|
|
if (m_process.state() != QProcess::NotRunning) {
|
|
disconnect(&m_process, &QProcess::errorOccurred, this, &QQmlDebugProcess::processError);
|
|
m_process.kill();
|
|
if (!m_process.waitForFinished(10000))
|
|
qFatal("Debug process refused to be killed. We need to crash now.");
|
|
}
|
|
}
|
|
|
|
void QQmlDebugProcess::setMaximumBindErrors(int ignore)
|
|
{
|
|
m_maximumBindErrors = ignore;
|
|
}
|
|
|
|
void QQmlDebugProcess::timeout()
|
|
{
|
|
qWarning() << "Timeout while waiting for QML debugging messages "
|
|
"in application output. Process is in state" << m_process.state()
|
|
<< ", Output:" << m_output << ".";
|
|
}
|
|
|
|
bool QQmlDebugProcess::waitForSessionStart()
|
|
{
|
|
if (m_process.state() != QProcess::Running) {
|
|
qWarning() << "Could not start up " << m_executable;
|
|
return false;
|
|
} else if (m_state == SessionStarted) {
|
|
return true;
|
|
} else if (m_state == SessionFailed) {
|
|
return false;
|
|
}
|
|
|
|
if (!QTest::qWaitFor(
|
|
[this]() { return m_state == SessionFailed || m_state == SessionStarted; },
|
|
15000)) {
|
|
timeout();
|
|
}
|
|
|
|
return m_state == SessionStarted;
|
|
}
|
|
|
|
int QQmlDebugProcess::debugPort() const
|
|
{
|
|
return m_port;
|
|
}
|
|
|
|
bool QQmlDebugProcess::waitForFinished()
|
|
{
|
|
return m_process.waitForFinished();
|
|
}
|
|
|
|
QProcess::ProcessState QQmlDebugProcess::state() const
|
|
{
|
|
return m_process.state();
|
|
}
|
|
|
|
QProcess::ExitStatus QQmlDebugProcess::exitStatus() const
|
|
{
|
|
return m_process.exitStatus();
|
|
}
|
|
|
|
void QQmlDebugProcess::addEnvironment(const QString &environment)
|
|
{
|
|
m_environment.append(environment);
|
|
}
|
|
|
|
QString QQmlDebugProcess::output() const
|
|
{
|
|
return m_output;
|
|
}
|
|
|
|
void QQmlDebugProcess::processAppOutput()
|
|
{
|
|
bool outputFromAppItself = false;
|
|
|
|
QString newOutput = m_process.readAll();
|
|
m_output.append(newOutput);
|
|
m_outputBuffer.append(newOutput);
|
|
|
|
while (true) {
|
|
const int nlIndex = m_outputBuffer.indexOf(QLatin1Char('\n'));
|
|
if (nlIndex < 0) // no further complete lines
|
|
break;
|
|
const QString line = m_outputBuffer.left(nlIndex);
|
|
m_outputBuffer = m_outputBuffer.right(m_outputBuffer.size() - nlIndex - 1);
|
|
|
|
if (line.contains("QML Debugger:")) {
|
|
auto portRx = QRegularExpression("Waiting for connection on port (\\d+)").match(line);
|
|
if (portRx.hasMatch()) {
|
|
m_port = portRx.captured(1).toInt();
|
|
m_state = SessionStarted;
|
|
continue;
|
|
}
|
|
if (line.contains("Unable to listen")) {
|
|
if (++m_receivedBindErrors >= m_maximumBindErrors) {
|
|
if (m_maximumBindErrors == 0)
|
|
qWarning() << "App was unable to bind to port!";
|
|
m_state = SessionFailed;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// set to true if there is output not coming from the debugger or we don't understand it
|
|
outputFromAppItself = true;
|
|
}
|
|
|
|
if (outputFromAppItself)
|
|
emit readyReadStandardOutput();
|
|
}
|
|
|
|
void QQmlDebugProcess::processError(QProcess::ProcessError error)
|
|
{
|
|
qDebug() << "An error occurred while waiting for debug process to become available:";
|
|
switch (error) {
|
|
case QProcess::FailedToStart:
|
|
qDebug() << "Process failed to start.";
|
|
break;
|
|
case QProcess::Crashed:
|
|
qDebug() << "Process crashed.";
|
|
break;
|
|
case QProcess::Timedout:
|
|
qDebug() << "Process timed out.";
|
|
break;
|
|
case QProcess::WriteError:
|
|
qDebug() << "Error while writing to process.";
|
|
break;
|
|
case QProcess::ReadError:
|
|
qDebug() << "Error while reading from process.";
|
|
break;
|
|
case QProcess::UnknownError:
|
|
qDebug() << "Unknown process error.";
|
|
break;
|
|
}
|
|
}
|