forked from qt-creator/qt-creator
Revert "Android: Improve application output window by adding filters"
This reverts commit d4ca232d54 and fixes
QML profiling on android.
Task-number: QTCREATORBUG-18120
Change-Id: I09b9062da197a4c6c0a7034f98a2bb0b41f1d559
Reviewed-by: Vikas Pachdha <vikas.pachdha@qt.io>
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
@@ -186,16 +186,6 @@ AndroidDebugSupport::AndroidDebugSupport(AndroidRunConfiguration *runConfig,
|
|||||||
QTC_ASSERT(m_runControl, return);
|
QTC_ASSERT(m_runControl, return);
|
||||||
m_runControl->showMessage(output, AppOutput);
|
m_runControl->showMessage(output, AppOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
QTC_ASSERT(runControl, return);
|
|
||||||
auto formatter = qobject_cast<AndroidOutputFormatter*>(runControl->outputFormatter());
|
|
||||||
QTC_ASSERT(formatter, return);
|
|
||||||
connect(m_runner, &AndroidRunner::pidFound, formatter, &AndroidOutputFormatter::appendPid);
|
|
||||||
connect(m_runner, &AndroidRunner::pidLost, formatter, &AndroidOutputFormatter::removePid);
|
|
||||||
connect(m_runner, &AndroidRunner::remoteProcessFinished, formatter,
|
|
||||||
[formatter] {
|
|
||||||
formatter->removePid(-1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidDebugSupport::handleRemoteProcessStarted(Utils::Port gdbServerPort, Utils::Port qmlPort)
|
void AndroidDebugSupport::handleRemoteProcessStarted(Utils::Port gdbServerPort, Utils::Port qmlPort)
|
||||||
|
|||||||
@@ -29,205 +29,16 @@
|
|||||||
#include "androidmanager.h"
|
#include "androidmanager.h"
|
||||||
|
|
||||||
#include <projectexplorer/kitinformation.h>
|
#include <projectexplorer/kitinformation.h>
|
||||||
#include <projectexplorer/projectexplorer.h>
|
|
||||||
#include <projectexplorer/projectexplorersettings.h>
|
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
#include <qtsupport/qtoutputformatter.h>
|
#include <qtsupport/qtoutputformatter.h>
|
||||||
#include <qtsupport/qtkitinformation.h>
|
#include <qtsupport/qtkitinformation.h>
|
||||||
#include <QPlainTextEdit>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QToolButton>
|
|
||||||
|
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/utilsicons.h>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
|
|
||||||
namespace Android {
|
namespace Android {
|
||||||
|
|
||||||
static QRegularExpression logCatRegExp("([0-9\\-]*\\s+[0-9\\-:.]*)" // 1. time
|
|
||||||
"\\s*"
|
|
||||||
"([DEIVWF])" // 2. log level
|
|
||||||
"\\/"
|
|
||||||
"(.*)" // 3. TAG
|
|
||||||
"\\(\\s*"
|
|
||||||
"(\\d+)" // 4. PID
|
|
||||||
"\\)\\:\\s"
|
|
||||||
"(.*)"); // 5. Message
|
|
||||||
|
|
||||||
AndroidOutputFormatter::AndroidOutputFormatter(Project *project)
|
|
||||||
: QtSupport::QtOutputFormatter(project)
|
|
||||||
, m_filtersButton(new QToolButton)
|
|
||||||
{
|
|
||||||
auto filtersMenu = new QMenu(m_filtersButton.data());
|
|
||||||
|
|
||||||
m_filtersButton->setToolTip(tr("Filters"));
|
|
||||||
m_filtersButton->setIcon(Utils::Icons::FILTER.icon());
|
|
||||||
m_filtersButton->setProperty("noArrow", true);
|
|
||||||
m_filtersButton->setAutoRaise(true);
|
|
||||||
m_filtersButton->setPopupMode(QToolButton::InstantPopup);
|
|
||||||
m_filtersButton->setMenu(filtersMenu);
|
|
||||||
|
|
||||||
auto logsMenu = filtersMenu->addMenu(tr("Log Level"));
|
|
||||||
addLogAction(All, logsMenu, tr("All"));
|
|
||||||
addLogAction(Verbose, logsMenu, tr("Verbose"));
|
|
||||||
addLogAction(Info, logsMenu, tr("Info"));
|
|
||||||
addLogAction(Debug, logsMenu, tr("Debug"));
|
|
||||||
addLogAction(Warning, logsMenu, tr("Warning"));
|
|
||||||
addLogAction(Error, logsMenu, tr("Error"));
|
|
||||||
addLogAction(Fatal, logsMenu, tr("Fatal"));
|
|
||||||
updateLogMenu();
|
|
||||||
m_appsMenu = filtersMenu->addMenu(tr("Applications"));
|
|
||||||
appendPid(-1, tr("All"));
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidOutputFormatter::~AndroidOutputFormatter()
|
|
||||||
{}
|
|
||||||
|
|
||||||
QList<QWidget *> AndroidOutputFormatter::toolbarWidgets() const
|
|
||||||
{
|
|
||||||
return QList<QWidget *>{m_filtersButton.data()};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::appendMessage(const QString &text, Utils::OutputFormat format)
|
|
||||||
{
|
|
||||||
if (text.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
CachedLine line;
|
|
||||||
line.content = text;
|
|
||||||
|
|
||||||
if (format < Utils::StdOutFormat) {
|
|
||||||
line.level = SkipFiltering;
|
|
||||||
line.pid = -1;
|
|
||||||
} else {
|
|
||||||
QRegularExpressionMatch match = logCatRegExp.match(text);
|
|
||||||
if (!match.hasMatch())
|
|
||||||
return;
|
|
||||||
line.level = None;
|
|
||||||
|
|
||||||
switch (match.captured(2).toLatin1()[0]) {
|
|
||||||
case 'D': line.level = Debug; break;
|
|
||||||
case 'I': line.level = Info; break;
|
|
||||||
case 'V': line.level = Verbose; break;
|
|
||||||
case 'W': line.level = Warning; break;
|
|
||||||
case 'E': line.level = Error; break;
|
|
||||||
case 'F': line.level = Fatal; break;
|
|
||||||
default: return;
|
|
||||||
}
|
|
||||||
line.pid = match.captured(4).toLongLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_cachedLines.append(line);
|
|
||||||
if (m_cachedLines.size() > ProjectExplorerPlugin::projectExplorerSettings().maxAppOutputLines)
|
|
||||||
m_cachedLines.pop_front();
|
|
||||||
|
|
||||||
filterMessage(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::clear()
|
|
||||||
{
|
|
||||||
m_cachedLines.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::appendPid(qint64 pid, const QString &name)
|
|
||||||
{
|
|
||||||
if (m_pids.contains(pid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto action = m_appsMenu->addAction(name);
|
|
||||||
m_pids[pid] = action;
|
|
||||||
action->setCheckable(true);
|
|
||||||
action->setChecked(pid != -1);
|
|
||||||
connect(action, &QAction::triggered, this, &AndroidOutputFormatter::applyFilter);
|
|
||||||
applyFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::removePid(qint64 pid)
|
|
||||||
{
|
|
||||||
if (pid == -1) {
|
|
||||||
for (auto action : m_pids)
|
|
||||||
m_appsMenu->removeAction(action);
|
|
||||||
m_pids.clear();
|
|
||||||
} else {
|
|
||||||
m_appsMenu->removeAction(m_pids[pid]);
|
|
||||||
m_pids.remove(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::updateLogMenu(LogLevel set, LogLevel reset)
|
|
||||||
{
|
|
||||||
m_logLevelFlags |= set;
|
|
||||||
m_logLevelFlags &= ~reset;
|
|
||||||
for (const auto & pair : m_logLevels)
|
|
||||||
pair.second->setChecked((m_logLevelFlags & pair.first) == pair.first);
|
|
||||||
|
|
||||||
applyFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::filterMessage(const CachedLine &line)
|
|
||||||
{
|
|
||||||
if (line.level == SkipFiltering || m_pids[-1]->isChecked()) {
|
|
||||||
QtOutputFormatter::appendMessage(line.content, Utils::NormalMessageFormat);
|
|
||||||
} else {
|
|
||||||
// Filter Log Level
|
|
||||||
if (!(m_logLevelFlags & line.level))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Filter PIDs
|
|
||||||
if (!m_pids[-1]->isChecked()) {
|
|
||||||
auto it = m_pids.find(line.pid);
|
|
||||||
if (it == m_pids.end() || !(*it)->isChecked())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::OutputFormat format = Utils::NormalMessageFormat;
|
|
||||||
switch (line.level) {
|
|
||||||
case Debug:
|
|
||||||
format = Utils::DebugFormat;
|
|
||||||
break;
|
|
||||||
case Info:
|
|
||||||
case Verbose:
|
|
||||||
format = Utils::StdOutFormat;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Warning:
|
|
||||||
case Error:
|
|
||||||
case Fatal:
|
|
||||||
format = Utils::StdErrFormat;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::OutputFormatter::appendMessage(line.content, format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputFormatter::applyFilter()
|
|
||||||
{
|
|
||||||
if (!plainTextEdit())
|
|
||||||
return;
|
|
||||||
|
|
||||||
plainTextEdit()->clear();
|
|
||||||
if (!m_pids[-1]->isChecked()) {
|
|
||||||
bool allOn = true;
|
|
||||||
for (auto action : m_pids) {
|
|
||||||
if (!action->isChecked()) {
|
|
||||||
allOn = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_pids[-1]->setChecked(allOn);
|
|
||||||
} else {
|
|
||||||
for (auto action : m_pids)
|
|
||||||
action->setChecked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &line : m_cachedLines)
|
|
||||||
filterMessage(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id)
|
AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id)
|
||||||
: RunConfiguration(parent, id)
|
: RunConfiguration(parent, id)
|
||||||
{
|
{
|
||||||
@@ -245,7 +56,7 @@ QWidget *AndroidRunConfiguration::createConfigurationWidget()
|
|||||||
|
|
||||||
Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const
|
Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const
|
||||||
{
|
{
|
||||||
return new AndroidOutputFormatter(target()->project());
|
return new QtSupport::QtOutputFormatter(target()->project());
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString AndroidRunConfiguration::remoteChannel() const
|
const QString AndroidRunConfiguration::remoteChannel() const
|
||||||
|
|||||||
@@ -28,9 +28,6 @@
|
|||||||
#include "android_global.h"
|
#include "android_global.h"
|
||||||
|
|
||||||
#include <projectexplorer/runconfiguration.h>
|
#include <projectexplorer/runconfiguration.h>
|
||||||
#include <qtsupport/qtoutputformatter.h>
|
|
||||||
|
|
||||||
#include <QMenu>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QToolButton;
|
class QToolButton;
|
||||||
@@ -38,65 +35,6 @@ QT_END_NAMESPACE
|
|||||||
|
|
||||||
namespace Android {
|
namespace Android {
|
||||||
|
|
||||||
class AndroidOutputFormatter : public QtSupport::QtOutputFormatter
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum LogLevel {
|
|
||||||
None = 0,
|
|
||||||
Verbose = 1,
|
|
||||||
Info = 1 << 1,
|
|
||||||
Debug = 1 << 2,
|
|
||||||
Warning = 1 << 3,
|
|
||||||
Error = 1 << 4,
|
|
||||||
Fatal = 1 << 5,
|
|
||||||
All = Verbose | Info | Debug | Warning | Error | Fatal,
|
|
||||||
SkipFiltering = ~All
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AndroidOutputFormatter(ProjectExplorer::Project *project);
|
|
||||||
~AndroidOutputFormatter();
|
|
||||||
|
|
||||||
// OutputFormatter interface
|
|
||||||
QList<QWidget*> toolbarWidgets() const override;
|
|
||||||
void appendMessage(const QString &text, Utils::OutputFormat format) override;
|
|
||||||
void clear() override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void appendPid(qint64 pid, const QString &name);
|
|
||||||
void removePid(qint64 pid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct CachedLine {
|
|
||||||
qint64 pid;
|
|
||||||
LogLevel level;
|
|
||||||
QString content;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updateLogMenu(LogLevel set = None, LogLevel reset = None);
|
|
||||||
void filterMessage(const CachedLine &line);
|
|
||||||
|
|
||||||
void applyFilter();
|
|
||||||
void addLogAction(LogLevel level, QMenu *logsMenu, const QString &name) {
|
|
||||||
auto action = logsMenu->addAction(name);
|
|
||||||
m_logLevels.push_back(qMakePair(level, action));
|
|
||||||
action->setCheckable(true);
|
|
||||||
connect(action, &QAction::triggered, this, [level, this](bool checked) {
|
|
||||||
updateLogMenu(checked ? level : None , checked ? None : level);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_logLevelFlags = All;
|
|
||||||
QVector<QPair<LogLevel, QAction*>> m_logLevels;
|
|
||||||
QHash<qint64, QAction*> m_pids;
|
|
||||||
QScopedPointer<QToolButton> m_filtersButton;
|
|
||||||
QMenu *m_appsMenu;
|
|
||||||
QList<CachedLine> m_cachedLines;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ANDROID_EXPORT AndroidRunConfiguration : public ProjectExplorer::RunConfiguration
|
class ANDROID_EXPORT AndroidRunConfiguration : public ProjectExplorer::RunConfiguration
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|||||||
@@ -62,13 +62,6 @@ void AndroidRunControl::start()
|
|||||||
this, &AndroidRunControl::handleRemoteOutput);
|
this, &AndroidRunControl::handleRemoteOutput);
|
||||||
connect(m_runner, &AndroidRunner::remoteProcessFinished,
|
connect(m_runner, &AndroidRunner::remoteProcessFinished,
|
||||||
this, &AndroidRunControl::handleRemoteProcessFinished);
|
this, &AndroidRunControl::handleRemoteProcessFinished);
|
||||||
|
|
||||||
auto formatter = static_cast<AndroidOutputFormatter *>(outputFormatter());
|
|
||||||
connect(m_runner, &AndroidRunner::pidFound,
|
|
||||||
formatter, &AndroidOutputFormatter::appendPid);
|
|
||||||
connect(m_runner, &AndroidRunner::pidLost,
|
|
||||||
formatter, &AndroidOutputFormatter::removePid);
|
|
||||||
|
|
||||||
appendMessage(tr("Starting remote process."), Utils::NormalMessageFormat);
|
appendMessage(tr("Starting remote process."), Utils::NormalMessageFormat);
|
||||||
m_runner->setRunnable(runnable().as<AndroidRunnable>());
|
m_runner->setRunnable(runnable().as<AndroidRunnable>());
|
||||||
m_runner->start();
|
m_runner->start();
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
#include <QTime>
|
#include <QTime>
|
||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QRegularExpression>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
@@ -127,10 +126,10 @@ namespace Internal {
|
|||||||
|
|
||||||
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
|
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
|
||||||
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
|
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
|
||||||
static const QString pidScript = QStringLiteral("input keyevent KEYCODE_WAKEUP; "
|
static const QString pidScript = QStringLiteral("for p in /proc/[0-9]*; "
|
||||||
"while true; do sleep 1; echo \"=\"; "
|
"do cat <$p/cmdline && echo :${p##*/}; done");
|
||||||
"for p in /proc/[0-9]*; "
|
static const QString pidPollingScript = QStringLiteral("while true; do sleep 1; "
|
||||||
"do cat <$p/cmdline && echo :${p##*/}; done; done");
|
"cat /proc/%1/cmdline > /dev/null; done");
|
||||||
|
|
||||||
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
||||||
"\\s+"
|
"\\s+"
|
||||||
@@ -148,26 +147,55 @@ static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
|||||||
);
|
);
|
||||||
static int APP_START_TIMEOUT = 45000;
|
static int APP_START_TIMEOUT = 45000;
|
||||||
|
|
||||||
enum class PidStatus {
|
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
|
||||||
Found,
|
int msecs = APP_START_TIMEOUT)
|
||||||
Lost
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PidInfo
|
|
||||||
{
|
{
|
||||||
PidInfo(qint64 pid = -1, PidStatus status = PidStatus::Lost, QString name = {})
|
bool timedOut = false;
|
||||||
: pid(pid)
|
auto end = chrono::high_resolution_clock::now();
|
||||||
, status(status)
|
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
||||||
, name(name)
|
timedOut = true;
|
||||||
{}
|
return timedOut;
|
||||||
qint64 pid;
|
}
|
||||||
PidStatus status;
|
|
||||||
QString name;
|
static qint64 extractPID(const QByteArray &output, const QString &packageName)
|
||||||
};
|
{
|
||||||
|
qint64 pid = -1;
|
||||||
|
foreach (auto tuple, output.split('\n')) {
|
||||||
|
tuple = tuple.simplified();
|
||||||
|
if (!tuple.isEmpty()) {
|
||||||
|
auto parts = tuple.split(':');
|
||||||
|
QString commandName = QString::fromLocal8Bit(parts.first());
|
||||||
|
if (parts.length() == 2 && commandName == packageName) {
|
||||||
|
pid = parts.last().toLongLong();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
|
||||||
|
QStringList selector, const QString &packageName)
|
||||||
|
{
|
||||||
|
if (packageName.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
qint64 processPID = -1;
|
||||||
|
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
|
||||||
|
do {
|
||||||
|
QThread::msleep(200);
|
||||||
|
const QByteArray out = Utils::SynchronousProcess()
|
||||||
|
.runBlocking(adbPath, selector << QStringLiteral("shell") << pidScript)
|
||||||
|
.allRawOutput();
|
||||||
|
processPID = extractPID(out, packageName);
|
||||||
|
} while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
|
||||||
|
|
||||||
|
if (!fi.isCanceled())
|
||||||
|
fi.reportResult(processPID);
|
||||||
|
}
|
||||||
|
|
||||||
static void deleter(QProcess *p)
|
static void deleter(QProcess *p)
|
||||||
{
|
{
|
||||||
p->disconnect();
|
|
||||||
p->kill();
|
p->kill();
|
||||||
p->waitForFinished();
|
p->waitForFinished();
|
||||||
// Might get deleted from its own signal handler.
|
// Might get deleted from its own signal handler.
|
||||||
@@ -201,31 +229,29 @@ signals:
|
|||||||
|
|
||||||
void remoteOutput(const QString &output);
|
void remoteOutput(const QString &output);
|
||||||
void remoteErrorOutput(const QString &output);
|
void remoteErrorOutput(const QString &output);
|
||||||
void pidFound(qint64, const QString &name);
|
|
||||||
void pidLost(qint64);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void findProcessPids();
|
void onProcessIdChanged(qint64 pid);
|
||||||
void onProcessIdChanged(PidInfo pidInfo);
|
|
||||||
void logcatReadStandardError();
|
void logcatReadStandardError();
|
||||||
void logcatReadStandardOutput();
|
void logcatReadStandardOutput();
|
||||||
void adbKill(qint64 pid);
|
void adbKill(qint64 pid);
|
||||||
QStringList selector() const { return m_selector; }
|
QStringList selector() const { return m_selector; }
|
||||||
void forceStop();
|
void forceStop();
|
||||||
|
void findPs();
|
||||||
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
||||||
bool adbShellAmNeedsQuotes();
|
bool adbShellAmNeedsQuotes();
|
||||||
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
|
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
|
||||||
int deviceSdkVersion();
|
|
||||||
|
|
||||||
// Create the processes and timer in the worker thread, for correct thread affinity
|
// Create the processes and timer in the worker thread, for correct thread affinity
|
||||||
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
|
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
|
||||||
std::unique_ptr<QProcess, decltype(&deleter)> m_pidsFinderProcess;
|
std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
|
||||||
QScopedPointer<QTcpSocket> m_socket;
|
QScopedPointer<QTcpSocket> m_socket;
|
||||||
|
|
||||||
QByteArray m_stdoutBuffer;
|
QByteArray m_stdoutBuffer;
|
||||||
QByteArray m_stderrBuffer;
|
QByteArray m_stderrBuffer;
|
||||||
|
|
||||||
QSet<qint64> m_processPids;
|
QFuture<qint64> m_pidFinder;
|
||||||
|
qint64 m_processPID = -1;
|
||||||
bool m_useCppDebugger = false;
|
bool m_useCppDebugger = false;
|
||||||
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
|
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
|
||||||
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
|
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
|
||||||
@@ -236,20 +262,20 @@ private:
|
|||||||
QString m_gdbserverSocket;
|
QString m_gdbserverSocket;
|
||||||
QString m_adb;
|
QString m_adb;
|
||||||
QStringList m_selector;
|
QStringList m_selector;
|
||||||
|
QRegExp m_logCatRegExp;
|
||||||
DebugHandShakeType m_handShakeMethod = SocketHandShake;
|
DebugHandShakeType m_handShakeMethod = SocketHandShake;
|
||||||
bool m_customPort = false;
|
bool m_customPort = false;
|
||||||
|
|
||||||
QString m_packageName;
|
QString m_packageName;
|
||||||
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
||||||
QByteArray m_pidsBuffer;
|
|
||||||
QScopedPointer<QTimer> m_timeoutTimer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
|
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
|
||||||
const QString &packageName, const QStringList &selector)
|
const QString &packageName, const QStringList &selector)
|
||||||
: m_adbLogcatProcess(nullptr, deleter)
|
: m_adbLogcatProcess(nullptr, deleter)
|
||||||
, m_pidsFinderProcess(nullptr, deleter)
|
, m_psIsAlive(nullptr, deleter)
|
||||||
, m_selector(selector)
|
, m_selector(selector)
|
||||||
|
, m_logCatRegExp(regExpLogcat)
|
||||||
, m_packageName(packageName)
|
, m_packageName(packageName)
|
||||||
{
|
{
|
||||||
Debugger::DebuggerRunConfigurationAspect *aspect
|
Debugger::DebuggerRunConfigurationAspect *aspect
|
||||||
@@ -313,18 +339,23 @@ AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Cor
|
|||||||
|
|
||||||
AndroidRunnerWorker::~AndroidRunnerWorker()
|
AndroidRunnerWorker::~AndroidRunnerWorker()
|
||||||
{
|
{
|
||||||
|
if (!m_pidFinder.isFinished())
|
||||||
|
m_pidFinder.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::forceStop()
|
void AndroidRunnerWorker::forceStop()
|
||||||
{
|
{
|
||||||
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
|
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
|
||||||
|
|
||||||
for (auto it = m_processPids.constBegin(); it != m_processPids.constEnd(); ++it) {
|
// try killing it via kill -9
|
||||||
emit pidLost(*it);
|
const QByteArray out = Utils::SynchronousProcess()
|
||||||
adbKill(*it);
|
.runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript)
|
||||||
|
.allRawOutput();
|
||||||
|
|
||||||
|
qint64 pid = extractPID(out.simplified(), m_packageName);
|
||||||
|
if (pid != -1) {
|
||||||
|
adbKill(pid);
|
||||||
}
|
}
|
||||||
m_processPids.clear();
|
|
||||||
m_pidsBuffer.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
||||||
@@ -338,12 +369,8 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
|||||||
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
||||||
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
|
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
|
||||||
this, &AndroidRunnerWorker::logcatReadStandardError);
|
this, &AndroidRunnerWorker::logcatReadStandardError);
|
||||||
|
|
||||||
// Its assumed that the device or avd returned by selector() is online.
|
// Its assumed that the device or avd returned by selector() is online.
|
||||||
QStringList logcatArgs = selector() << "logcat" << "-v" << "time";
|
logcatProcess->start(m_adb, selector() << "logcat");
|
||||||
if (deviceSdkVersion() > 20)
|
|
||||||
logcatArgs << "-T" << "0";
|
|
||||||
logcatProcess->start(m_adb, logcatArgs);
|
|
||||||
|
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
|
|
||||||
@@ -481,20 +508,9 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
|||||||
|
|
||||||
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
||||||
m_adbLogcatProcess = std::move(logcatProcess);
|
m_adbLogcatProcess = std::move(logcatProcess);
|
||||||
|
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
|
||||||
m_timeoutTimer.reset(new QTimer);
|
m_packageName),
|
||||||
m_timeoutTimer->setSingleShot(true);
|
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
|
||||||
connect(m_timeoutTimer.data(), &QTimer::timeout,
|
|
||||||
this,[this] { onProcessIdChanged(PidInfo{}); });
|
|
||||||
m_timeoutTimer->start(APP_START_TIMEOUT);
|
|
||||||
|
|
||||||
m_pidsFinderProcess.reset(new QProcess);
|
|
||||||
m_pidsFinderProcess->setProcessChannelMode(QProcess::MergedChannels);
|
|
||||||
connect(m_pidsFinderProcess.get(), &QProcess::readyRead, this, &AndroidRunnerWorker::findProcessPids);
|
|
||||||
connect(m_pidsFinderProcess.get(),
|
|
||||||
static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
|
||||||
this, [this] { onProcessIdChanged(PidInfo{}); });
|
|
||||||
m_pidsFinderProcess->start(m_adb, selector() << "shell" << pidScript);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
|
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
|
||||||
@@ -530,19 +546,6 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage,
|
|||||||
return response.result == Utils::SynchronousProcessResponse::Finished;
|
return response.result == Utils::SynchronousProcessResponse::Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AndroidRunnerWorker::deviceSdkVersion()
|
|
||||||
{
|
|
||||||
Utils::SynchronousProcess adb;
|
|
||||||
adb.setTimeoutS(10);
|
|
||||||
Utils::SynchronousProcessResponse response
|
|
||||||
= adb.run(m_adb, selector() << "shell" << "getprop" << "ro.build.version.sdk");
|
|
||||||
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|
|
||||||
|| response.result != Utils::SynchronousProcessResponse::Finished)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return response.allOutput().trimmed().toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
||||||
{
|
{
|
||||||
if (m_useCppDebugger) {
|
if (m_useCppDebugger) {
|
||||||
@@ -556,79 +559,21 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
|||||||
|
|
||||||
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
|
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
|
||||||
}
|
}
|
||||||
QTC_CHECK(!m_processPids.isEmpty());
|
QTC_CHECK(m_processPID != -1);
|
||||||
}
|
}
|
||||||
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
|
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::findProcessPids()
|
|
||||||
{
|
|
||||||
static QMap<qint64, QByteArray> extractedPids;
|
|
||||||
static auto oldPids = m_processPids;
|
|
||||||
|
|
||||||
m_pidsBuffer += m_pidsFinderProcess->readAll();
|
|
||||||
while (!m_pidsBuffer.isEmpty()) {
|
|
||||||
const int to = m_pidsBuffer.indexOf('\n');
|
|
||||||
if (to < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (to == 0) {
|
|
||||||
m_pidsBuffer = m_pidsBuffer.mid(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// = is used to delimit ps outputs
|
|
||||||
// is needed to know when an existins PID is killed
|
|
||||||
if (m_pidsBuffer[0] != '=') {
|
|
||||||
QByteArray tuple = m_pidsBuffer.left(to + 1).simplified();
|
|
||||||
QList<QByteArray> parts = tuple.split(':');
|
|
||||||
QByteArray commandName = parts.takeFirst();
|
|
||||||
if (QString::fromLocal8Bit(commandName) == m_packageName) {
|
|
||||||
auto pid = parts.last().toLongLong();
|
|
||||||
if (!m_processPids.contains(pid)) {
|
|
||||||
extractedPids[pid] = commandName + (parts.length() == 2
|
|
||||||
? ":" + parts.first() : QByteArray{});
|
|
||||||
} else {
|
|
||||||
oldPids.remove(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add new PIDs
|
|
||||||
for (auto it = extractedPids.constBegin(); it != extractedPids.constEnd(); ++it) {
|
|
||||||
onProcessIdChanged(PidInfo(it.key(), PidStatus::Found,
|
|
||||||
QString::fromLocal8Bit(it.value())));
|
|
||||||
}
|
|
||||||
extractedPids.clear();
|
|
||||||
|
|
||||||
// Remove the dead ones
|
|
||||||
for (auto it = oldPids.constBegin(); it != oldPids.constEnd(); ++it)
|
|
||||||
onProcessIdChanged(PidInfo(*it, PidStatus::Lost));
|
|
||||||
|
|
||||||
// Save the current non dead PIDs
|
|
||||||
oldPids = m_processPids;
|
|
||||||
if (m_processPids.isEmpty()) {
|
|
||||||
extractedPids.clear();
|
|
||||||
m_pidsBuffer.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_pidsBuffer = m_pidsBuffer.mid(to + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
|
void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
|
||||||
{
|
{
|
||||||
m_timeoutTimer.reset();
|
if (!m_pidFinder.isFinished())
|
||||||
m_pidsFinderProcess.reset();
|
m_pidFinder.cancel();
|
||||||
if (!m_processPids.isEmpty())
|
|
||||||
forceStop();
|
|
||||||
|
|
||||||
|
if (m_processPID != -1) {
|
||||||
|
forceStop();
|
||||||
|
}
|
||||||
foreach (const QStringList &entry, adbCommands)
|
foreach (const QStringList &entry, adbCommands)
|
||||||
runAdb(selector() << entry);
|
runAdb(selector() << entry);
|
||||||
|
|
||||||
m_adbLogcatProcess.reset();
|
|
||||||
emit remoteProcessFinished(QLatin1String("\n\n") +
|
|
||||||
tr("\"%1\" terminated.").arg(m_packageName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
|
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
|
||||||
@@ -650,48 +595,58 @@ void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buff
|
|||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString pidString = QString::number(m_processPID);
|
||||||
foreach (const QByteArray &msg, lines) {
|
foreach (const QByteArray &msg, lines) {
|
||||||
const QString line = QString::fromUtf8(msg.trimmed());
|
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
|
||||||
if (onlyError)
|
if (!line.contains(pidString))
|
||||||
|
continue;
|
||||||
|
if (m_logCatRegExp.exactMatch(line)) {
|
||||||
|
// Android M
|
||||||
|
if (m_logCatRegExp.cap(1) == pidString) {
|
||||||
|
const QString &messagetype = m_logCatRegExp.cap(2);
|
||||||
|
QString output = line.mid(m_logCatRegExp.pos(2));
|
||||||
|
|
||||||
|
if (onlyError
|
||||||
|
|| messagetype == QLatin1String("F")
|
||||||
|
|| messagetype == QLatin1String("E")
|
||||||
|
|| messagetype == QLatin1String("W"))
|
||||||
|
emit remoteErrorOutput(output);
|
||||||
|
else
|
||||||
|
emit remoteOutput(output);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (onlyError || line.startsWith("F/")
|
||||||
|
|| line.startsWith("E/")
|
||||||
|
|| line.startsWith("W/"))
|
||||||
emit remoteErrorOutput(line);
|
emit remoteErrorOutput(line);
|
||||||
else
|
else
|
||||||
emit remoteOutput(line);
|
emit remoteOutput(line);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
|
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
|
||||||
{
|
{
|
||||||
// Don't write to m_psProc from a different thread
|
// Don't write to m_psProc from a different thread
|
||||||
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
||||||
|
m_processPID = pid;
|
||||||
auto isFirst = m_processPids.isEmpty();
|
if (m_processPID == -1) {
|
||||||
if (pidInfo.status == PidStatus::Lost) {
|
|
||||||
m_processPids.remove(pidInfo.pid);
|
|
||||||
emit pidLost(pidInfo.pid);
|
|
||||||
} else {
|
|
||||||
m_processPids.insert(pidInfo.pid);
|
|
||||||
emit pidFound(pidInfo.pid, pidInfo.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_processPids.isEmpty() || pidInfo.pid == -1) {
|
|
||||||
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
|
||||||
.arg(m_packageName));
|
.arg(m_packageName));
|
||||||
// App died/killed. Reset log and monitor processes.
|
// App died/killed. Reset log and monitor processes.
|
||||||
forceStop();
|
|
||||||
m_adbLogcatProcess.reset();
|
m_adbLogcatProcess.reset();
|
||||||
m_timeoutTimer.reset();
|
m_psIsAlive.reset();
|
||||||
} else if (isFirst) {
|
} else {
|
||||||
m_timeoutTimer.reset();
|
|
||||||
if (m_useCppDebugger) {
|
if (m_useCppDebugger) {
|
||||||
// This will be funneled to the engine to actually start and attach
|
// This will be funneled to the engine to actually start and attach
|
||||||
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
||||||
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
|
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
|
||||||
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
emit remoteServerRunning(serverChannel, m_processPID);
|
||||||
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
|
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
|
||||||
// This will be funneled to the engine to actually start and attach
|
// This will be funneled to the engine to actually start and attach
|
||||||
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
||||||
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
|
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
|
||||||
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
emit remoteServerRunning(serverChannel, m_processPID);
|
||||||
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
|
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
|
||||||
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
|
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
|
||||||
} else {
|
} else {
|
||||||
@@ -699,18 +654,27 @@ void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
|
|||||||
emit remoteProcessStarted(Utils::Port(), Utils::Port());
|
emit remoteProcessStarted(Utils::Port(), Utils::Port());
|
||||||
}
|
}
|
||||||
logcatReadStandardOutput();
|
logcatReadStandardOutput();
|
||||||
|
QTC_ASSERT(!m_psIsAlive, /**/);
|
||||||
|
m_psIsAlive.reset(new QProcess);
|
||||||
|
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
connect(m_psIsAlive.get(), &QProcess::readyRead, [this](){
|
||||||
|
if (!m_psIsAlive->readAll().simplified().isEmpty())
|
||||||
|
onProcessIdChanged(-1);
|
||||||
|
});
|
||||||
|
m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
|
||||||
|
<< pidPollingScript.arg(m_processPID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::logcatReadStandardError()
|
void AndroidRunnerWorker::logcatReadStandardError()
|
||||||
{
|
{
|
||||||
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
if (m_processPID != -1)
|
||||||
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
|
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidRunnerWorker::logcatReadStandardOutput()
|
void AndroidRunnerWorker::logcatReadStandardOutput()
|
||||||
{
|
{
|
||||||
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
if (m_processPID != -1)
|
||||||
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
|
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,10 +725,6 @@ AndroidRunner::AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig
|
|||||||
this, &AndroidRunner::remoteOutput);
|
this, &AndroidRunner::remoteOutput);
|
||||||
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
|
||||||
this, &AndroidRunner::remoteErrorOutput);
|
this, &AndroidRunner::remoteErrorOutput);
|
||||||
connect(m_worker.data(), &AndroidRunnerWorker::pidFound,
|
|
||||||
this, &AndroidRunner::pidFound);
|
|
||||||
connect(m_worker.data(), &AndroidRunnerWorker::pidLost,
|
|
||||||
this, &AndroidRunner::pidLost);
|
|
||||||
|
|
||||||
m_thread.start();
|
m_thread.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ signals:
|
|||||||
void adbParametersChanged(const QString &packageName, const QStringList &selector);
|
void adbParametersChanged(const QString &packageName, const QStringList &selector);
|
||||||
void avdDetected();
|
void avdDetected();
|
||||||
|
|
||||||
void pidFound(qint64, const QString &name);
|
|
||||||
void pidLost(qint64);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAVD();
|
void checkAVD();
|
||||||
void launchAVD();
|
void launchAVD();
|
||||||
|
|||||||
Reference in New Issue
Block a user