2018-03-17 09:31:56 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "androidrunnerworker.h"
|
|
|
|
|
|
2018-05-15 10:08:16 +02:00
|
|
|
#include "androidconfigurations.h"
|
|
|
|
|
#include "androidconstants.h"
|
|
|
|
|
#include "androidmanager.h"
|
|
|
|
|
#include "androidrunconfiguration.h"
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
#include <debugger/debuggerrunconfigurationaspect.h>
|
2018-07-31 12:21:47 +02:00
|
|
|
#include <projectexplorer/runconfigurationaspects.h>
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <projectexplorer/target.h>
|
|
|
|
|
#include <qtsupport/baseqtversion.h>
|
|
|
|
|
#include <qtsupport/qtkitinformation.h>
|
|
|
|
|
#include <utils/hostosinfo.h>
|
|
|
|
|
#include <utils/runextensions.h>
|
|
|
|
|
#include <utils/synchronousprocess.h>
|
|
|
|
|
#include <utils/temporaryfile.h>
|
2018-07-31 12:21:47 +02:00
|
|
|
#include <utils/qtcprocess.h>
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <utils/url.h>
|
|
|
|
|
|
2018-06-18 11:49:14 +02:00
|
|
|
#include <QLoggingCategory>
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <QTcpServer>
|
2018-05-15 10:08:16 +02:00
|
|
|
#include <QThread>
|
|
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <chrono>
|
|
|
|
|
|
2018-06-18 11:49:14 +02:00
|
|
|
namespace {
|
|
|
|
|
Q_LOGGING_CATEGORY(androidRunWorkerLog, "qtc.android.run.androidrunnerworker")
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
using namespace std;
|
|
|
|
|
using namespace std::placeholders;
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
|
|
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const QString pidScript = "pidof -s \"%1\"";
|
|
|
|
|
static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
|
|
|
|
|
"do cat <$p/cmdline && echo :${p##*/}; done");
|
|
|
|
|
static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done");
|
|
|
|
|
|
|
|
|
|
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
|
|
|
|
"\\s+"
|
|
|
|
|
"[0-9\\-:.]*"// time
|
|
|
|
|
"\\s*"
|
|
|
|
|
"(\\d*)" // pid 1. capture
|
|
|
|
|
"\\s+"
|
|
|
|
|
"\\d*" // unknown
|
|
|
|
|
"\\s+"
|
|
|
|
|
"(\\w)" // message type 2. capture
|
|
|
|
|
"\\s+"
|
|
|
|
|
"(.*): " // source 3. capture
|
|
|
|
|
"(.*)" // message 4. capture
|
|
|
|
|
"[\\n\\r]*"
|
|
|
|
|
);
|
|
|
|
|
static int APP_START_TIMEOUT = 45000;
|
|
|
|
|
|
|
|
|
|
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
|
|
|
|
|
int msecs = APP_START_TIMEOUT)
|
|
|
|
|
{
|
|
|
|
|
bool timedOut = false;
|
|
|
|
|
auto end = chrono::high_resolution_clock::now();
|
|
|
|
|
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
|
|
|
|
timedOut = true;
|
|
|
|
|
return timedOut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
static void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
|
|
|
|
|
QStringList selector, const QString &packageName,
|
|
|
|
|
bool preNougat)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Finding PID. PreNougat:" << preNougat;
|
2018-03-17 09:31:56 +02:00
|
|
|
if (packageName.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
qint64 processPID = -1;
|
|
|
|
|
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
selector.append("shell");
|
|
|
|
|
selector.append(preNougat ? pidScriptPreNougat : pidScript.arg(packageName));
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
QThread::msleep(200);
|
2018-05-09 12:20:54 +02:00
|
|
|
const QByteArray out = Utils::SynchronousProcess().runBlocking(adbPath, selector).allRawOutput();
|
|
|
|
|
if (preNougat) {
|
|
|
|
|
processPID = extractPID(out, packageName);
|
|
|
|
|
} else {
|
|
|
|
|
if (!out.isEmpty())
|
|
|
|
|
processPID = out.trimmed().toLongLong();
|
|
|
|
|
}
|
2018-03-17 09:31:56 +02:00
|
|
|
} while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
|
|
|
|
|
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "PID found:" << processPID;
|
2018-03-17 09:31:56 +02:00
|
|
|
if (!fi.isCanceled())
|
|
|
|
|
fi.reportResult(processPID);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 11:54:32 +02:00
|
|
|
static void deleter(QProcess *p)
|
|
|
|
|
{
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Killing process:" << p->objectName();
|
2018-05-08 11:54:32 +02:00
|
|
|
p->terminate();
|
|
|
|
|
if (!p->waitForFinished(1000)) {
|
|
|
|
|
p->kill();
|
|
|
|
|
p->waitForFinished();
|
|
|
|
|
}
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Done killing process:" << p->objectName();
|
2018-05-08 11:54:32 +02:00
|
|
|
// Might get deleted from its own signal handler.
|
|
|
|
|
p->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 12:50:41 +02:00
|
|
|
AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packageName)
|
|
|
|
|
: m_packageName(packageName)
|
2018-03-17 09:31:56 +02:00
|
|
|
, m_adbLogcatProcess(nullptr, deleter)
|
|
|
|
|
, m_psIsAlive(nullptr, deleter)
|
|
|
|
|
, m_logCatRegExp(regExpLogcat)
|
|
|
|
|
, m_gdbServerProcess(nullptr, deleter)
|
|
|
|
|
, m_jdbProcess(nullptr, deleter)
|
|
|
|
|
|
|
|
|
|
{
|
2018-05-15 12:39:56 +02:00
|
|
|
auto runConfig = runner->runControl()->runConfiguration();
|
2018-03-17 09:31:56 +02:00
|
|
|
auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
|
2018-05-15 12:39:56 +02:00
|
|
|
Core::Id runMode = runner->runMode();
|
2018-03-17 09:31:56 +02:00
|
|
|
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
|
|
|
|
|
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
|
|
|
|
|
if (debuggingMode && aspect->useQmlDebugger())
|
|
|
|
|
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
|
|
|
|
|
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
|
|
|
|
|
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
|
|
|
|
|
else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
|
|
|
|
|
m_qmlDebugServices = QmlDebug::QmlPreviewServices;
|
|
|
|
|
else
|
|
|
|
|
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
|
|
|
|
|
m_localGdbServerPort = Utils::Port(5039);
|
|
|
|
|
QTC_CHECK(m_localGdbServerPort.isValid());
|
|
|
|
|
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "QML debugging enabled";
|
2018-03-17 09:31:56 +02:00
|
|
|
QTcpServer server;
|
2018-07-05 08:13:08 +02:00
|
|
|
QTC_ASSERT(server.listen(QHostAddress::LocalHost),
|
2018-03-17 09:31:56 +02:00
|
|
|
qDebug() << tr("No free ports available on host for QML debugging."));
|
|
|
|
|
m_qmlServer.setScheme(Utils::urlTcpScheme());
|
|
|
|
|
m_qmlServer.setHost(server.serverAddress().toString());
|
|
|
|
|
m_qmlServer.setPort(server.serverPort());
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "QML server:" << m_qmlServer.toDisplayString();
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
|
|
|
|
|
m_localJdbServerPort = Utils::Port(5038);
|
|
|
|
|
QTC_CHECK(m_localJdbServerPort.isValid());
|
2018-05-07 17:13:52 +02:00
|
|
|
|
|
|
|
|
auto target = runConfig->target();
|
|
|
|
|
m_deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
|
|
|
|
|
m_apiLevel = AndroidManager::deviceApiLevel(target);
|
2018-05-15 10:08:16 +02:00
|
|
|
|
2018-07-31 12:21:47 +02:00
|
|
|
m_extraAppParams = runConfig->runnable().commandLineArguments;
|
|
|
|
|
|
2018-05-15 12:11:54 +02:00
|
|
|
if (auto aspect = runConfig->extraAspect(Constants::ANDROID_AMSTARTARGS))
|
2018-05-15 10:08:16 +02:00
|
|
|
m_amStartExtraArgs = static_cast<BaseStringAspect *>(aspect)->value().split(' ');
|
2018-05-15 12:11:54 +02:00
|
|
|
|
|
|
|
|
if (auto aspect = runConfig->extraAspect(Constants::ANDROID_PRESTARTSHELLCMDLIST)) {
|
|
|
|
|
for (const QString &shellCmd : static_cast<BaseStringListAspect *>(aspect)->value())
|
|
|
|
|
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
|
|
|
|
|
}
|
|
|
|
|
for (const QString &shellCmd : runner->recordedData(Constants::ANDROID_PRESTARTSHELLCMDLIST).toStringList())
|
|
|
|
|
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
|
|
|
|
|
|
|
|
|
|
if (auto aspect = runConfig->extraAspect(Constants::ANDROID_POSTFINISHSHELLCMDLIST)) {
|
|
|
|
|
for (const QString &shellCmd : static_cast<BaseStringListAspect *>(aspect)->value())
|
|
|
|
|
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
|
|
|
|
|
}
|
|
|
|
|
for (const QString &shellCmd : runner->recordedData(Constants::ANDROID_POSTFINISHSHELLCMDLIST).toStringList())
|
|
|
|
|
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
|
2018-06-18 11:49:14 +02:00
|
|
|
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Device Serial:" << m_deviceSerialNumber
|
|
|
|
|
<< "API level:" << m_apiLevel
|
|
|
|
|
<< "Extra Start Args:" << m_amStartExtraArgs
|
|
|
|
|
<< "Before Start ADB cmds:" << m_beforeStartAdbCommands
|
|
|
|
|
<< "After finish ADB cmds:" << m_afterFinishAdbCommands;
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
AndroidRunnerWorker::~AndroidRunnerWorker()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
forceStop();
|
|
|
|
|
|
|
|
|
|
if (!m_pidFinder.isFinished())
|
|
|
|
|
m_pidFinder.cancel();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
// Between Android SDK Tools version 24.3.1 and 24.3.4 the quoting
|
|
|
|
|
// needs for the 'adb shell am start ...' parameters changed.
|
|
|
|
|
// Run a test to find out on what side of the fence we live.
|
|
|
|
|
// The command will fail with a complaint about the "--dummy"
|
|
|
|
|
// option on newer SDKs, and with "No intent supplied" on older ones.
|
|
|
|
|
// In case the test itself fails assume a new SDK.
|
|
|
|
|
Utils::SynchronousProcess adb;
|
|
|
|
|
adb.setTimeoutS(10);
|
|
|
|
|
Utils::SynchronousProcessResponse response
|
|
|
|
|
= adb.run(m_adb, selector() << "shell" << "am" << "start"
|
|
|
|
|
<< "-e" << "dummy" << "dummy --dummy");
|
|
|
|
|
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|
|
|
|
|
|| response.result != Utils::SynchronousProcessResponse::Finished)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
const QString output = response.allOutput();
|
|
|
|
|
const bool oldSdk = output.contains("Error: No intent supplied");
|
|
|
|
|
return !oldSdk;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
bool AndroidRunnerWorker::runAdb(const QStringList &args, int timeoutS)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-06-18 11:49:14 +02:00
|
|
|
QStringList adbArgs = selector() + args;
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "ADB command: " << m_adb << adbArgs.join(' ');
|
2018-03-17 09:31:56 +02:00
|
|
|
Utils::SynchronousProcess adb;
|
|
|
|
|
adb.setTimeoutS(timeoutS);
|
2018-06-18 11:49:14 +02:00
|
|
|
Utils::SynchronousProcessResponse response = adb.run(m_adb, adbArgs);
|
2018-03-17 09:31:56 +02:00
|
|
|
m_lastRunAdbError = response.exitMessage(m_adb, timeoutS);
|
|
|
|
|
m_lastRunAdbRawOutput = response.allRawOutput();
|
2018-06-18 11:49:14 +02:00
|
|
|
bool success = response.result == Utils::SynchronousProcessResponse::Finished;
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "ADB command result:" << success << response.allRawOutput();
|
|
|
|
|
return success;
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::adbKill(qint64 pid)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
runAdb({"shell", "kill", "-9", QString::number(pid)});
|
2018-05-15 12:50:41 +02:00
|
|
|
runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)});
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
QStringList AndroidRunnerWorker::selector() const
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-05-07 17:13:52 +02:00
|
|
|
return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber);
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::forceStop()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-05-15 12:50:41 +02:00
|
|
|
runAdb({"shell", "am", "force-stop", m_packageName}, 30);
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
// try killing it via kill -9
|
|
|
|
|
const QByteArray out = Utils::SynchronousProcess()
|
|
|
|
|
.runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScriptPreNougat)
|
|
|
|
|
.allRawOutput();
|
|
|
|
|
|
2018-05-15 12:50:41 +02:00
|
|
|
qint64 pid = extractPID(out.simplified(), m_packageName);
|
2018-03-17 09:31:56 +02:00
|
|
|
if (pid != -1) {
|
|
|
|
|
adbKill(pid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::logcatReadStandardError()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::logcatReadStandardOutput()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
QList<QByteArray> lines = text.split('\n');
|
|
|
|
|
// lines always contains at least one item
|
|
|
|
|
lines[0].prepend(buffer);
|
|
|
|
|
if (!lines.last().endsWith('\n')) {
|
|
|
|
|
// incomplete line
|
|
|
|
|
buffer = lines.last();
|
|
|
|
|
lines.removeLast();
|
|
|
|
|
} else {
|
|
|
|
|
buffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString pidString = QString::number(m_processPID);
|
|
|
|
|
foreach (const QByteArray &msg, lines) {
|
|
|
|
|
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
|
|
|
|
|
if (!line.contains(pidString))
|
|
|
|
|
continue;
|
|
|
|
|
if (m_useCppDebugger) {
|
|
|
|
|
switch (m_jdbState) {
|
|
|
|
|
case JDBState::Idle:
|
|
|
|
|
if (msg.trimmed().endsWith("Sending WAIT chunk")) {
|
|
|
|
|
m_jdbState = JDBState::Waiting;
|
|
|
|
|
handleJdbWaiting();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case JDBState::Waiting:
|
|
|
|
|
if (msg.indexOf("debugger has settled") > 0) {
|
|
|
|
|
m_jdbState = JDBState::Settled;
|
|
|
|
|
handleJdbSettled();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
else
|
|
|
|
|
emit remoteOutput(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::setAndroidDeviceInfo(const AndroidDeviceInfo &info)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-05-07 17:13:52 +02:00
|
|
|
m_deviceSerialNumber = info.serialNumber;
|
|
|
|
|
m_apiLevel = info.sdk;
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Android Device Info changed"
|
|
|
|
|
<< m_deviceSerialNumber << m_apiLevel;
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::asyncStartHelper()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
forceStop();
|
|
|
|
|
|
|
|
|
|
// Start the logcat process before app starts.
|
2018-05-08 11:54:32 +02:00
|
|
|
std::unique_ptr<QProcess, Deleter> logcatProcess(new QProcess, deleter);
|
2018-03-17 09:31:56 +02:00
|
|
|
connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
|
2018-05-09 12:20:54 +02:00
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
2018-03-17 09:31:56 +02:00
|
|
|
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
|
2018-05-09 12:20:54 +02:00
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardError);
|
2018-03-17 09:31:56 +02:00
|
|
|
// Its assumed that the device or avd returned by selector() is online.
|
|
|
|
|
logcatProcess->start(m_adb, selector() << "logcat");
|
|
|
|
|
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
|
|
|
|
m_adbLogcatProcess = std::move(logcatProcess);
|
2018-06-18 11:49:14 +02:00
|
|
|
m_adbLogcatProcess->setObjectName("AdbLogcatProcess");
|
2018-05-15 12:11:54 +02:00
|
|
|
for (const QString &entry : m_beforeStartAdbCommands)
|
2018-03-17 09:31:56 +02:00
|
|
|
runAdb(entry.split(' ', QString::SkipEmptyParts));
|
|
|
|
|
|
|
|
|
|
QStringList args({"shell", "am", "start"});
|
2018-05-15 10:08:16 +02:00
|
|
|
args << m_amStartExtraArgs;
|
2018-05-15 09:52:09 +02:00
|
|
|
args << "-n" << m_intentName;
|
2018-03-17 09:31:56 +02:00
|
|
|
if (m_useCppDebugger) {
|
|
|
|
|
args << "-D";
|
|
|
|
|
QString gdbServerSocket;
|
|
|
|
|
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
|
2018-05-15 12:50:41 +02:00
|
|
|
if (!runAdb({"shell", "run-as", m_packageName, "/system/bin/sh", "-c", "pwd"})) {
|
2018-03-17 09:31:56 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
gdbServerSocket = QString::fromUtf8(m_lastRunAdbRawOutput.trimmed()) + "/debug-socket";
|
|
|
|
|
|
|
|
|
|
QString gdbServerExecutable;
|
2018-05-15 12:50:41 +02:00
|
|
|
if (!runAdb({"shell", "run-as", m_packageName, "ls", "lib/"})) {
|
2018-03-17 09:31:56 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto &line: m_lastRunAdbRawOutput.split('\n')) {
|
|
|
|
|
if (line.indexOf("gdbserver") != -1/* || line.indexOf("lldb-server") != -1*/) {
|
|
|
|
|
gdbServerExecutable = QString::fromUtf8(line.trimmed());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gdbServerExecutable.isEmpty()) {
|
2018-06-08 13:57:32 +02:00
|
|
|
emit remoteProcessFinished(tr("Cannot find C++ debugger."));
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 12:50:41 +02:00
|
|
|
runAdb({"shell", "run-as", m_packageName, "killall", gdbServerExecutable});
|
|
|
|
|
runAdb({"shell", "run-as", m_packageName, "rm", gdbServerSocket});
|
2018-05-08 11:54:32 +02:00
|
|
|
std::unique_ptr<QProcess, Deleter> gdbServerProcess(new QProcess, deleter);
|
2018-03-17 09:31:56 +02:00
|
|
|
gdbServerProcess->start(m_adb, selector() << "shell" << "run-as"
|
2018-05-15 12:50:41 +02:00
|
|
|
<< m_packageName << "lib/" + gdbServerExecutable
|
2018-03-17 09:31:56 +02:00
|
|
|
<< "--multi" << "+" + gdbServerSocket);
|
|
|
|
|
if (!gdbServerProcess->waitForStarted()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to start C++ debugger."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_gdbServerProcess = std::move(gdbServerProcess);
|
2018-06-18 11:49:14 +02:00
|
|
|
m_gdbServerProcess->setObjectName("GdbServerProcess");
|
2018-03-17 09:31:56 +02:00
|
|
|
QStringList removeForward{"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()};
|
|
|
|
|
runAdb(removeForward);
|
|
|
|
|
if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(),
|
|
|
|
|
"localfilesystem:" + gdbServerSocket})) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(m_lastRunAdbError));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-05-15 12:11:54 +02:00
|
|
|
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
|
|
|
|
|
// currently forward to same port on device and host
|
|
|
|
|
const QString port = QString("tcp:%1").arg(m_qmlServer.port());
|
|
|
|
|
QStringList removeForward{{"forward", "--remove", port}};
|
|
|
|
|
runAdb(removeForward);
|
|
|
|
|
if (!runAdb({"forward", port, port})) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
|
2018-05-31 10:07:41 +02:00
|
|
|
.arg(m_lastRunAdbError) + "\n" + m_lastRunAdbRawOutput);
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2018-05-15 12:11:54 +02:00
|
|
|
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
args << "-e" << "qml_debug" << "true"
|
|
|
|
|
<< "-e" << "qmljsdebugger"
|
|
|
|
|
<< QString("port:%1,block,services:%2")
|
|
|
|
|
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 12:21:47 +02:00
|
|
|
|
2018-05-07 18:06:30 +02:00
|
|
|
if (!m_extraAppParams.isEmpty()) {
|
2018-07-31 12:21:47 +02:00
|
|
|
QStringList appArgs =
|
|
|
|
|
Utils::QtcProcess::splitArgs(m_extraAppParams, Utils::OsType::OsTypeLinux);
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Using application arguments: " << appArgs;
|
2018-04-17 10:09:35 +02:00
|
|
|
args << "-e" << "extraappparams"
|
2018-07-31 12:21:47 +02:00
|
|
|
<< QString::fromLatin1(appArgs.join(' ').toUtf8().toBase64());
|
2018-04-17 10:09:35 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-07 18:06:30 +02:00
|
|
|
if (m_extraEnvVars.size() > 0) {
|
2018-04-17 10:09:35 +02:00
|
|
|
args << "-e" << "extraenvvars"
|
2018-05-07 18:06:30 +02:00
|
|
|
<< QString::fromLatin1(m_extraEnvVars.toStringList().join('\t')
|
2018-04-17 10:09:35 +02:00
|
|
|
.toUtf8().toBase64());
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
if (!runAdb(args)) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
|
|
|
|
|
.arg(m_lastRunAdbError));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::asyncStart()
|
|
|
|
|
{
|
|
|
|
|
asyncStartHelper();
|
|
|
|
|
|
|
|
|
|
m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPID, m_adb, selector(),
|
2018-05-15 12:50:41 +02:00
|
|
|
m_packageName, m_isPreNougat),
|
2018-05-09 12:20:54 +02:00
|
|
|
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunnerWorker::asyncStop()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
if (!m_pidFinder.isFinished())
|
|
|
|
|
m_pidFinder.cancel();
|
|
|
|
|
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
forceStop();
|
|
|
|
|
|
|
|
|
|
m_jdbProcess.reset();
|
|
|
|
|
m_gdbServerProcess.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::handleJdbWaiting()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
QStringList removeForward{"forward", "--remove", "tcp:" + m_localJdbServerPort.toString()};
|
|
|
|
|
runAdb(removeForward);
|
|
|
|
|
if (!runAdb({"forward", "tcp:" + m_localJdbServerPort.toString(),
|
|
|
|
|
"jdwp:" + QString::number(m_processPID)})) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward jdb debugging ports. Reason: %1.").arg(m_lastRunAdbError));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-05-15 12:11:54 +02:00
|
|
|
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
auto jdbPath = AndroidConfigurations::currentConfig().openJDKLocation().appendPath("bin");
|
|
|
|
|
if (Utils::HostOsInfo::isWindowsHost())
|
|
|
|
|
jdbPath.appendPath("jdb.exe");
|
|
|
|
|
else
|
|
|
|
|
jdbPath.appendPath("jdb");
|
|
|
|
|
|
2018-06-18 11:49:14 +02:00
|
|
|
QStringList jdbArgs("-connect");
|
|
|
|
|
jdbArgs << QString("com.sun.jdi.SocketAttach:hostname=localhost,port=%1")
|
|
|
|
|
.arg(m_localJdbServerPort.toString());
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Starting JDB:" << jdbPath << jdbArgs.join(' ');
|
2018-05-08 11:54:32 +02:00
|
|
|
std::unique_ptr<QProcess, Deleter> jdbProcess(new QProcess, &deleter);
|
2018-03-17 09:31:56 +02:00
|
|
|
jdbProcess->setProcessChannelMode(QProcess::MergedChannels);
|
2018-06-18 11:49:14 +02:00
|
|
|
jdbProcess->start(jdbPath.toString(), jdbArgs);
|
2018-03-17 09:31:56 +02:00
|
|
|
if (!jdbProcess->waitForStarted()) {
|
2018-07-13 15:25:38 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to start jdb."));
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_jdbProcess = std::move(jdbProcess);
|
2018-06-18 11:49:14 +02:00
|
|
|
m_jdbProcess->setObjectName("JdbProcess");
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::handleJdbSettled()
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Handle JDB settled";
|
2018-03-17 09:31:56 +02:00
|
|
|
auto waitForCommand = [&]() {
|
|
|
|
|
for (int i= 0; i < 5 && m_jdbProcess->state() == QProcess::Running; ++i) {
|
|
|
|
|
m_jdbProcess->waitForReadyRead(500);
|
|
|
|
|
QByteArray lines = m_jdbProcess->readAll();
|
|
|
|
|
for (const auto &line: lines.split('\n')) {
|
|
|
|
|
auto msg = line.trimmed();
|
|
|
|
|
if (msg.startsWith(">"))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
if (waitForCommand()) {
|
|
|
|
|
m_jdbProcess->write("cont\n");
|
|
|
|
|
if (m_jdbProcess->waitForBytesWritten(5000) && waitForCommand()) {
|
|
|
|
|
m_jdbProcess->write("exit\n");
|
|
|
|
|
m_jdbProcess->waitForBytesWritten(5000);
|
|
|
|
|
if (!m_jdbProcess->waitForFinished(5000)) {
|
|
|
|
|
m_jdbProcess->terminate();
|
|
|
|
|
if (!m_jdbProcess->waitForFinished(5000)) {
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Killing JDB process";
|
2018-03-17 09:31:56 +02:00
|
|
|
m_jdbProcess->kill();
|
|
|
|
|
m_jdbProcess->waitForFinished();
|
|
|
|
|
}
|
|
|
|
|
} else if (m_jdbProcess->exitStatus() == QProcess::NormalExit && m_jdbProcess->exitCode() == 0) {
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "JDB settled";
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-13 15:25:38 +02:00
|
|
|
emit remoteProcessFinished(tr("Cannot attach jdb to the running application. Reason: %1.")
|
|
|
|
|
.arg(m_lastRunAdbError));
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
// Don't write to m_psProc from a different thread
|
|
|
|
|
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
|
|
|
|
|
<< "to:" << pid;
|
2018-03-17 09:31:56 +02:00
|
|
|
m_processPID = pid;
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
|
2018-05-15 12:50:41 +02:00
|
|
|
.arg(m_packageName));
|
2018-03-17 09:31:56 +02:00
|
|
|
// App died/killed. Reset log, monitor, jdb & gdb processes.
|
|
|
|
|
m_adbLogcatProcess.reset();
|
|
|
|
|
m_psIsAlive.reset();
|
|
|
|
|
m_jdbProcess.reset();
|
|
|
|
|
m_gdbServerProcess.reset();
|
|
|
|
|
|
|
|
|
|
// Run adb commands after application quit.
|
2018-05-15 12:11:54 +02:00
|
|
|
for (const QString &entry: m_afterFinishAdbCommands)
|
2018-03-17 09:31:56 +02:00
|
|
|
runAdb(entry.split(' ', QString::SkipEmptyParts));
|
|
|
|
|
} else {
|
|
|
|
|
// In debugging cases this will be funneled to the engine to actually start
|
|
|
|
|
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
|
|
|
|
emit remoteProcessStarted(m_localGdbServerPort, m_qmlServer, m_processPID);
|
|
|
|
|
logcatReadStandardOutput();
|
|
|
|
|
QTC_ASSERT(!m_psIsAlive, /**/);
|
|
|
|
|
m_psIsAlive.reset(new QProcess);
|
2018-06-18 11:49:14 +02:00
|
|
|
m_psIsAlive->setObjectName("IsAliveProcess");
|
2018-03-17 09:31:56 +02:00
|
|
|
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
|
connect(m_psIsAlive.get(), static_cast<void(QProcess::*)(int)>(&QProcess::finished),
|
2018-05-09 12:20:54 +02:00
|
|
|
this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, -1));
|
2018-03-17 09:31:56 +02:00
|
|
|
m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
|
|
|
|
|
<< pidPollingScript.arg(m_processPID));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::setExtraEnvVars(const Utils::Environment &extraEnvVars)
|
2018-05-07 18:06:30 +02:00
|
|
|
{
|
|
|
|
|
m_extraEnvVars = extraEnvVars;
|
2018-06-18 11:49:14 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Settings extra env:"
|
|
|
|
|
<< extraEnvVars.toStringList();
|
2018-05-07 18:06:30 +02:00
|
|
|
}
|
|
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Android
|