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
|
|
|
|
2020-05-11 15:33:42 +02:00
|
|
|
#include <debugger/debuggerkitinformation.h>
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <debugger/debuggerrunconfigurationaspect.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
|
2018-08-02 11:00:06 +02:00
|
|
|
#include <projectexplorer/environmentaspect.h>
|
2018-07-31 12:21:47 +02:00
|
|
|
#include <projectexplorer/runconfigurationaspects.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
#include <projectexplorer/runcontrol.h>
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <projectexplorer/target.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
#include <qtsupport/baseqtversion.h>
|
|
|
|
|
#include <qtsupport/qtkitinformation.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
#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-10-01 14:22:49 +03:00
|
|
|
#include <utils/fileutils.h>
|
2018-03-17 09:31:56 +02:00
|
|
|
|
2020-05-18 15:31:13 +02:00
|
|
|
#include <QDir>
|
|
|
|
|
#include <QDirIterator>
|
2020-04-28 12:08:22 +02:00
|
|
|
#include <QFileInfo>
|
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 {
|
2020-01-15 14:39:23 +01:00
|
|
|
static Q_LOGGING_CATEGORY(androidRunWorkerLog, "qtc.android.run.androidrunnerworker", QtWarningMsg)
|
2018-12-20 18:40:34 +01:00
|
|
|
static const int GdbTempFileMaxCounter = 20;
|
2018-06-18 11:49:14 +02:00
|
|
|
}
|
|
|
|
|
|
2018-03-17 09:31:56 +02:00
|
|
|
using namespace std;
|
|
|
|
|
using namespace std::placeholders;
|
|
|
|
|
using namespace ProjectExplorer;
|
2019-06-06 16:27:55 +02:00
|
|
|
using namespace Utils;
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2019-11-19 18:27:35 +01:00
|
|
|
static const QString pidScript = "pidof -s '%1'";
|
2018-03-17 09:31:56 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-13 14:38:18 +01:00
|
|
|
static qint64 extractPID(const QByteArray &output, const QString &packageName)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
|
|
|
|
qint64 pid = -1;
|
|
|
|
|
foreach (auto tuple, output.split('\n')) {
|
|
|
|
|
tuple = tuple.simplified();
|
|
|
|
|
if (!tuple.isEmpty()) {
|
|
|
|
|
auto parts = tuple.split(':');
|
2019-03-13 14:38:18 +01:00
|
|
|
QString commandName = QString::fromLocal8Bit(parts.first());
|
2018-03-17 09:31:56 +02:00
|
|
|
if (parts.length() == 2 && commandName == packageName) {
|
|
|
|
|
pid = parts.last().toLongLong();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return pid;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 11:12:45 +02:00
|
|
|
static void findProcessPID(QFutureInterface<qint64> &fi, 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();
|
|
|
|
|
do {
|
|
|
|
|
QThread::msleep(200);
|
2019-06-06 16:27:55 +02:00
|
|
|
FilePath adbPath = AndroidConfigurations::currentConfig().adbToolPath();
|
2019-03-13 14:38:18 +01:00
|
|
|
selector.append("shell");
|
|
|
|
|
selector.append(preNougat ? pidScriptPreNougat : pidScript.arg(packageName));
|
2019-06-06 16:27:55 +02:00
|
|
|
const auto out = SynchronousProcess().runBlocking({adbPath, selector}).allRawOutput();
|
2018-05-09 12:20:54 +02:00
|
|
|
if (preNougat) {
|
2019-03-13 14:38:18 +01:00
|
|
|
processPID = extractPID(out, packageName);
|
2018-05-09 12:20:54 +02:00
|
|
|
} else {
|
2019-03-13 14:38:18 +01:00
|
|
|
if (!out.isEmpty())
|
|
|
|
|
processPID = out.trimmed().toLongLong();
|
2018-05-09 12:20:54 +02:00
|
|
|
}
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-15 04:56:26 +02:00
|
|
|
static QString gdbServerArch(const QString &androidAbi)
|
|
|
|
|
{
|
|
|
|
|
if (androidAbi == "arm64-v8a")
|
|
|
|
|
return QString("arm64");
|
|
|
|
|
if (androidAbi == "armeabi-v7a")
|
|
|
|
|
return QString("arm");
|
|
|
|
|
// That's correct for "x86_64" and "x86", and best guess at anything that will evolve:
|
|
|
|
|
return androidAbi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FilePath gdbServer(const QString &androidAbi, const QtSupport::BaseQtVersion *qtVersion)
|
|
|
|
|
{
|
|
|
|
|
const FilePath path = AndroidConfigurations::currentConfig().ndkLocation(qtVersion)
|
|
|
|
|
.pathAppended(QString("prebuilt/android-%1/gdbserver/gdbserver")
|
|
|
|
|
.arg(gdbServerArch(androidAbi)));
|
|
|
|
|
if (path.exists())
|
|
|
|
|
return path;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-15 05:17:27 +02:00
|
|
|
static QString lldbServerArch(const QString &androidAbi)
|
|
|
|
|
{
|
|
|
|
|
if (androidAbi == "armeabi-v7a")
|
|
|
|
|
return QString("armeabi");
|
|
|
|
|
// Correct for arm64-v8a "x86_64" and "x86", and best guess at anything that will evolve:
|
|
|
|
|
return androidAbi; // arm64-v8a, x86, x86_64
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-18 15:31:13 +02:00
|
|
|
static QString lldbServerArch2(const QString &androidAbi)
|
|
|
|
|
{
|
|
|
|
|
if (androidAbi == "armeabi-v7a")
|
|
|
|
|
return {"arm"};
|
|
|
|
|
if (androidAbi == "x86")
|
|
|
|
|
return {"i386"};
|
|
|
|
|
if (androidAbi == "arm64-v8a")
|
|
|
|
|
return {"aarch64"};
|
|
|
|
|
// Correct for "x86_64" a and best guess at anything that will evolve:
|
|
|
|
|
return androidAbi; // arm64-v8a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FilePath lldbServer(const QString &androidAbi, const QtSupport::BaseQtVersion *qtVersion)
|
2020-04-15 05:17:27 +02:00
|
|
|
{
|
|
|
|
|
const AndroidConfig &config = AndroidConfigurations::currentConfig();
|
2020-05-18 15:31:13 +02:00
|
|
|
const FilePath prebuilt = config.ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
|
|
|
|
|
const QString abiNeedle = lldbServerArch2(androidAbi);
|
|
|
|
|
|
|
|
|
|
// The new, built-in LLDB.
|
|
|
|
|
QDirIterator it(prebuilt.toString(), QDir::Files|QDir::Executable, QDirIterator::Subdirectories);
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
|
it.next();
|
|
|
|
|
const QString filePath = it.filePath();
|
|
|
|
|
if (filePath.endsWith(abiNeedle + "/lldb-server")) {
|
|
|
|
|
return FilePath::fromString(filePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 05:17:27 +02:00
|
|
|
|
2020-05-18 15:31:13 +02:00
|
|
|
// Older: Find LLDB version. sdk_definitions.json contains something like "lldb;3.1". Use that.
|
2020-04-15 05:17:27 +02:00
|
|
|
const QStringList packages = config.defaultEssentials();
|
|
|
|
|
for (const QString &package : packages) {
|
|
|
|
|
if (package.startsWith("lldb;")) {
|
|
|
|
|
const QString lldbVersion = package.mid(5);
|
|
|
|
|
const FilePath path = config.sdkLocation()
|
|
|
|
|
/ QString("lldb/%1/android/%2/lldb-server")
|
|
|
|
|
.arg(lldbVersion, lldbServerArch(androidAbi));
|
|
|
|
|
if (path.exists())
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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)
|
2020-04-15 04:33:06 +02:00
|
|
|
, m_debugServerProcess(nullptr, deleter)
|
2018-03-17 09:31:56 +02:00
|
|
|
, m_jdbProcess(nullptr, deleter)
|
|
|
|
|
|
|
|
|
|
{
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
auto runControl = runner->runControl();
|
2020-05-11 15:33:42 +02:00
|
|
|
m_useLldb = Debugger::DebuggerKitAspect::engineType(runControl->kit())
|
|
|
|
|
== Debugger::LldbEngineType;
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
auto aspect = runControl->aspect<Debugger::DebuggerRunConfigurationAspect>();
|
2019-03-13 15:53:28 +01:00
|
|
|
Core::Id runMode = runControl->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;
|
2020-04-15 04:33:06 +02:00
|
|
|
m_localDebugServerPort = Utils::Port(5039);
|
|
|
|
|
QTC_CHECK(m_localDebugServerPort.isValid());
|
2018-03-17 09:31:56 +02:00
|
|
|
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_localJdbServerPort = Utils::Port(5038);
|
|
|
|
|
QTC_CHECK(m_localJdbServerPort.isValid());
|
2018-05-07 17:13:52 +02:00
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
auto target = runControl->target();
|
2018-05-07 17:13:52 +02:00
|
|
|
m_deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
|
|
|
|
|
m_apiLevel = AndroidManager::deviceApiLevel(target);
|
2018-05-15 10:08:16 +02:00
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
m_extraEnvVars = runControl->aspect<EnvironmentAspect>()->environment();
|
2018-08-02 11:00:06 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Environment variables for the app"
|
|
|
|
|
<< m_extraEnvVars.toStringList();
|
|
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
m_extraAppParams = runControl->runnable().commandLineArguments;
|
2018-07-31 12:21:47 +02:00
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
if (auto aspect = runControl->aspect(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
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
if (auto aspect = runControl->aspect(Constants::ANDROID_PRESTARTSHELLCMDLIST)) {
|
2018-05-15 12:11:54 +02:00
|
|
|
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));
|
|
|
|
|
|
Avoid some visible uses of RunControl::runConfiguration()
For a long time, probably from the very beginning, a RunControl
was meant to hold (a copy of) data needed for its operation, that was
valid at the time of its construction, to be resilient in cases
where RunConfiguration setting were changed while the RunControl
was running, or to properly re-run with the original settings.
Unfortunately, the task was repetitive, as RunConfiguration
classes had no generic access to properties / "aspects"
and there was was the runConfiguration() accessor (probably
for mostly unrelated reasons in the output pane handling) which
made the idea of just casting that to the original runConfiguration
and access the data directly there appealing, with all the
expected consequences.
This patch here partially addresses the issue by copying some
more of the related data at RunControl construction time and
adjust the using code, avoiding most uses of the runConfiguration()
accessor in a mostly mechanical matter.
Complete removal appears possible, but will be less mechanical
in "difficult" plugins like ios, so this is left for later.
The new accessors in RunControl are very much ad-hoc, leaving
room for improvement, e.g. by consolidating the access to the
run config settings aspects with the other runconfig aspects
or similar. For now the goal is to remove the runConfiguration()
accessor, and to as much as possible fixed data after RunControl
setup is finished.
Next step would be to officially allow construction of RunControls
without a specific RunConfiguration by setting the necessary
data independently, removing the need for the various workarounds
that are currently used for the purpose of faking (parts of) the
effect of the non-existing RunConfiguration or refusing to operate
at all, even if it would be possible.
Change-Id: If8e5596da8422c70e90f97270389adbe6d0b46f2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-03-11 15:42:43 +01:00
|
|
|
if (auto aspect = runControl->aspect(Constants::ANDROID_POSTFINISHSHELLCMDLIST)) {
|
2018-05-15 12:11:54 +02:00
|
|
|
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;
|
2020-02-16 20:46:23 +02:00
|
|
|
QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit());
|
2019-11-21 11:03:03 +02:00
|
|
|
QString preferredAbi = AndroidManager::apkDevicePreferredAbi(target);
|
2020-04-15 05:17:27 +02:00
|
|
|
if (!preferredAbi.isEmpty()) {
|
|
|
|
|
if (m_useLldb)
|
2020-05-18 15:31:13 +02:00
|
|
|
m_debugServerPath = lldbServer(preferredAbi, version).toString();
|
2020-04-15 05:17:27 +02:00
|
|
|
else
|
|
|
|
|
m_debugServerPath = gdbServer(preferredAbi, version).toString();
|
|
|
|
|
}
|
2018-12-03 11:25:49 +02:00
|
|
|
m_useAppParamsForQmlDebugger = version->qtVersion() >= QtSupport::QtVersionNumber(5, 12);
|
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-08-07 11:12:45 +02:00
|
|
|
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *stdOut,
|
|
|
|
|
const QByteArray &writeData)
|
2018-03-17 09:31:56 +02:00
|
|
|
{
|
2018-06-18 11:49:14 +02:00
|
|
|
QStringList adbArgs = selector() + args;
|
2018-08-07 11:12:45 +02:00
|
|
|
SdkToolResult result = AndroidManager::runAdbCommand(adbArgs, writeData);
|
|
|
|
|
if (!result.success())
|
|
|
|
|
emit remoteErrorOutput(result.exitMessage() + "\n" + result.stdErr());
|
|
|
|
|
if (stdOut)
|
|
|
|
|
*stdOut = result.stdOut();
|
|
|
|
|
return result.success();
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-15 09:01:38 +02:00
|
|
|
bool AndroidRunnerWorker::uploadDebugServer(const QString &debugServerFileName)
|
2018-10-01 14:22:49 +03:00
|
|
|
{
|
2020-04-15 04:33:06 +02:00
|
|
|
// Push the gdbserver or lldb-server to temp location and then to package dir.
|
2018-12-20 18:40:34 +01:00
|
|
|
// the files can't be pushed directly to package because of permissions.
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Uploading GdbServer";
|
|
|
|
|
|
|
|
|
|
bool foundUnique = true;
|
|
|
|
|
auto cleanUp = [this, &foundUnique] (QString *p) {
|
|
|
|
|
if (foundUnique && !runAdb({"shell", "rm", "-f", *p}))
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Gdbserver cleanup failed.";
|
|
|
|
|
delete p;
|
|
|
|
|
};
|
|
|
|
|
std::unique_ptr<QString, decltype (cleanUp)>
|
|
|
|
|
tempGdbServerPath(new QString("/data/local/tmp/%1"), cleanUp);
|
|
|
|
|
|
2020-04-15 09:01:38 +02:00
|
|
|
// Get a unique temp file name for gdb/lldbserver copy
|
2018-12-20 18:40:34 +01:00
|
|
|
int count = 0;
|
|
|
|
|
while (deviceFileExists(tempGdbServerPath->arg(++count))) {
|
|
|
|
|
if (count > GdbTempFileMaxCounter) {
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Can not get temporary file name";
|
|
|
|
|
foundUnique = false;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*tempGdbServerPath = tempGdbServerPath->arg(count);
|
|
|
|
|
|
|
|
|
|
// Copy gdbserver to temp location
|
2020-04-15 04:33:06 +02:00
|
|
|
if (!runAdb({"push", m_debugServerPath , *tempGdbServerPath})) {
|
2018-12-20 18:40:34 +01:00
|
|
|
qCDebug(androidRunWorkerLog) << "Gdbserver upload to temp directory failed";
|
2018-10-01 14:22:49 +03:00
|
|
|
return false;
|
2018-12-20 18:40:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy gdbserver from temp location to app directory
|
2020-04-15 09:01:38 +02:00
|
|
|
if (!runAdb({"shell", "run-as", m_packageName, "cp" , *tempGdbServerPath, debugServerFileName})) {
|
2020-04-28 12:08:22 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Debug server copy from temp directory failed";
|
2018-10-01 14:22:49 +03:00
|
|
|
return false;
|
2018-12-20 18:40:34 +01:00
|
|
|
}
|
2020-04-15 09:01:38 +02:00
|
|
|
QTC_ASSERT(runAdb({"shell", "run-as", m_packageName, "chmod", "777", debugServerFileName}),
|
2020-04-28 12:08:22 +02:00
|
|
|
qCDebug(androidRunWorkerLog) << "Debug server chmod 777 failed.");
|
2018-12-20 18:40:34 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidRunnerWorker::deviceFileExists(const QString &filePath)
|
|
|
|
|
{
|
2019-02-04 15:21:55 +01:00
|
|
|
QString output;
|
|
|
|
|
const bool success = runAdb({"shell", "ls", filePath, "2>/dev/null"}, &output);
|
|
|
|
|
return success && !output.trimmed().isEmpty();
|
2018-12-20 18:40:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidRunnerWorker::packageFileExists(const QString &filePath)
|
|
|
|
|
{
|
2019-02-04 15:21:55 +01:00
|
|
|
QString output;
|
|
|
|
|
const bool success = runAdb({"shell", "run-as", m_packageName, "ls", filePath, "2>/dev/null"}, &output);
|
|
|
|
|
return success && !output.trimmed().isEmpty();
|
2018-10-01 14:22:49 +03: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-08-07 11:12:45 +02:00
|
|
|
runAdb({"shell", "am", "force-stop", m_packageName});
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
// try killing it via kill -9
|
2019-03-13 14:38:18 +01:00
|
|
|
if (m_processPID != -1)
|
|
|
|
|
adbKill(m_processPID);
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
// Its assumed that the device or avd returned by selector() is online.
|
2018-08-07 11:12:45 +02:00
|
|
|
// Start the logcat process before app starts.
|
2018-03-17 09:31:56 +02:00
|
|
|
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
2018-08-07 11:12:45 +02:00
|
|
|
m_adbLogcatProcess.reset(AndroidManager::runAdbCommandDetached(selector() << "logcat"));
|
|
|
|
|
if (m_adbLogcatProcess) {
|
|
|
|
|
m_adbLogcatProcess->setObjectName("AdbLogcatProcess");
|
|
|
|
|
connect(m_adbLogcatProcess.get(), &QProcess::readyReadStandardOutput,
|
|
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
|
|
|
|
connect(m_adbLogcatProcess.get(), &QProcess::readyReadStandardError,
|
|
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardError);
|
|
|
|
|
}
|
|
|
|
|
|
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";
|
|
|
|
|
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
|
2018-08-07 11:12:45 +02:00
|
|
|
QString packageDir;
|
|
|
|
|
if (!runAdb({"shell", "run-as", m_packageName, "/system/bin/sh", "-c", "pwd"},
|
|
|
|
|
&packageDir)) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to find application directory."));
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2018-08-06 11:56:20 +02:00
|
|
|
|
|
|
|
|
// Add executable flag to package dir. Gdb can't connect to running server on device on
|
|
|
|
|
// e.g. on Android 8 with NDK 10e
|
2018-08-07 11:12:45 +02:00
|
|
|
runAdb({"shell", "run-as", m_packageName, "chmod", "a+x", packageDir.trimmed()});
|
2018-03-17 09:31:56 +02:00
|
|
|
|
2020-05-18 10:13:46 +02:00
|
|
|
if (!QFileInfo::exists(m_debugServerPath)) {
|
|
|
|
|
QString msg = tr("Cannot find C++ debug server in NDK installation");
|
|
|
|
|
if (m_useLldb) {
|
|
|
|
|
msg += "\n" + tr("The lldb-server binary has not been found. Maybe "
|
|
|
|
|
"sdk_definitions.json does not contain 'lldb;x.y' as "
|
|
|
|
|
"sdk_essential_package or LLDB was not installed.");
|
|
|
|
|
}
|
|
|
|
|
emit remoteProcessFinished(msg);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-15 09:01:38 +02:00
|
|
|
QString debugServerFile;
|
|
|
|
|
if (m_useLldb) {
|
|
|
|
|
debugServerFile = "./lldb-server";
|
2020-05-18 11:47:03 +02:00
|
|
|
runAdb({"shell", "run-as", m_packageName, "killall", "lldb-server"});
|
|
|
|
|
if (!uploadDebugServer(debugServerFile)) {
|
|
|
|
|
emit remoteProcessFinished(tr("Cannot copy C++ debug server."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-04-15 09:01:38 +02:00
|
|
|
} else {
|
2020-05-18 08:46:17 +02:00
|
|
|
if (packageFileExists("./lib/gdbserver")) {
|
|
|
|
|
debugServerFile = "./lib/gdbserver";
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
|
|
|
|
|
runAdb({"shell", "run-as", m_packageName, "killall", "gdbserver"});
|
|
|
|
|
} else if (packageFileExists("./lib/libgdbserver.so")) {
|
|
|
|
|
debugServerFile = "./lib/libgdbserver.so";
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Found GDB server " + debugServerFile;
|
|
|
|
|
runAdb({"shell", "run-as", m_packageName, "killall", "libgdbserver.so"});
|
|
|
|
|
} else {
|
2020-04-15 09:01:38 +02:00
|
|
|
// Armv8. symlink lib is not available.
|
2020-05-18 08:46:17 +02:00
|
|
|
debugServerFile = "./gdbserver";
|
2020-04-15 09:01:38 +02:00
|
|
|
// Kill the previous instances of gdbserver. Do this before copying the gdbserver.
|
2020-05-18 08:46:17 +02:00
|
|
|
runAdb({"shell", "run-as", m_packageName, "killall", "gdbserver"});
|
2020-05-18 10:13:46 +02:00
|
|
|
if (!uploadDebugServer("./gdbserver")) {
|
|
|
|
|
emit remoteProcessFinished(tr("Cannot copy C++ debug server."));
|
2020-04-15 09:01:38 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2018-11-28 09:59:19 +01:00
|
|
|
}
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
2018-08-07 11:12:45 +02:00
|
|
|
QString debuggerServerErr;
|
2020-04-15 09:01:38 +02:00
|
|
|
if (!startDebuggerServer(packageDir, debugServerFile, &debuggerServerErr)) {
|
2018-08-07 11:12:45 +02:00
|
|
|
emit remoteProcessFinished(debuggerServerErr);
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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})) {
|
2018-08-07 11:12:45 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports."));
|
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
|
|
|
|
2018-12-03 11:25:49 +02:00
|
|
|
const QString qmljsdebugger = QString("port:%1,block,services:%2")
|
2018-03-17 09:31:56 +02:00
|
|
|
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
|
2018-12-03 11:25:49 +02:00
|
|
|
|
|
|
|
|
if (m_useAppParamsForQmlDebugger) {
|
|
|
|
|
if (!m_extraAppParams.isEmpty())
|
|
|
|
|
m_extraAppParams.prepend(' ');
|
|
|
|
|
m_extraAppParams.prepend("-qmljsdebugger=" + qmljsdebugger);
|
|
|
|
|
} else {
|
|
|
|
|
args << "-e" << "qml_debug" << "true"
|
|
|
|
|
<< "-e" << "qmljsdebugger"
|
|
|
|
|
<< qmljsdebugger;
|
|
|
|
|
}
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
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)) {
|
2019-03-01 17:10:38 +01:00
|
|
|
emit remoteProcessFinished(tr("Failed to start the activity."));
|
2018-03-17 09:31:56 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-02 22:07:22 +02:00
|
|
|
bool AndroidRunnerWorker::startDebuggerServer(const QString &packageDir,
|
2020-04-15 09:01:38 +02:00
|
|
|
const QString &debugServerFile,
|
2018-12-02 22:07:22 +02:00
|
|
|
QString *errorStr)
|
2018-08-07 11:12:45 +02:00
|
|
|
{
|
2020-04-15 09:01:38 +02:00
|
|
|
if (m_useLldb) {
|
|
|
|
|
QString lldbServerErr;
|
|
|
|
|
QStringList lldbServerArgs = selector();
|
|
|
|
|
lldbServerArgs << "shell" << "run-as" << m_packageName << debugServerFile
|
|
|
|
|
<< "platform"
|
|
|
|
|
<< "--server"
|
|
|
|
|
<< "--listen" << QString("*:%1").arg(m_localDebugServerPort.toString());
|
|
|
|
|
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(lldbServerArgs, &lldbServerErr));
|
|
|
|
|
|
|
|
|
|
if (!m_debugServerProcess) {
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Debugger process failed to start" << lldbServerErr;
|
|
|
|
|
if (errorStr)
|
|
|
|
|
*errorStr = tr("Failed to start debugger server.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Debugger process started";
|
|
|
|
|
m_debugServerProcess->setObjectName("AndroidDebugServerProcess");
|
2018-08-07 11:12:45 +02:00
|
|
|
|
2020-04-15 09:01:38 +02:00
|
|
|
} else {
|
|
|
|
|
QString gdbServerSocket = packageDir + "/debug-socket";
|
|
|
|
|
runAdb({"shell", "run-as", m_packageName, "rm", gdbServerSocket});
|
|
|
|
|
|
|
|
|
|
QString gdbProcessErr;
|
|
|
|
|
QStringList gdbServerErr = selector();
|
|
|
|
|
gdbServerErr << "shell" << "run-as" << m_packageName << debugServerFile
|
|
|
|
|
<< "--multi" << "+" + gdbServerSocket;
|
|
|
|
|
m_debugServerProcess.reset(AndroidManager::runAdbCommandDetached(gdbServerErr, &gdbProcessErr));
|
|
|
|
|
|
|
|
|
|
if (!m_debugServerProcess) {
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Debugger process failed to start" << gdbServerErr;
|
|
|
|
|
if (errorStr)
|
|
|
|
|
*errorStr = tr("Failed to start debugger server.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
qCDebug(androidRunWorkerLog) << "Debugger process started";
|
|
|
|
|
m_debugServerProcess->setObjectName("AndroidDebugServerProcess");
|
|
|
|
|
|
|
|
|
|
QStringList removeForward{"forward", "--remove", "tcp:" + m_localDebugServerPort.toString()};
|
|
|
|
|
runAdb(removeForward);
|
|
|
|
|
if (!runAdb({"forward", "tcp:" + m_localDebugServerPort.toString(),
|
|
|
|
|
"localfilesystem:" + gdbServerSocket})) {
|
|
|
|
|
if (errorStr)
|
|
|
|
|
*errorStr = tr("Failed to forward C++ debugging ports.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
|
2018-08-07 11:12:45 +02:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 12:20:54 +02:00
|
|
|
void AndroidRunnerWorker::asyncStart()
|
|
|
|
|
{
|
|
|
|
|
asyncStartHelper();
|
|
|
|
|
|
2018-08-07 11:12:45 +02:00
|
|
|
m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPID, 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();
|
2020-04-15 04:33:06 +02:00
|
|
|
m_debugServerProcess.reset();
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
|
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)})) {
|
2019-03-01 17:10:38 +01:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward JDB debugging ports."));
|
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
|
|
|
|
2019-05-17 13:20:41 +02:00
|
|
|
auto jdbPath = AndroidConfigurations::currentConfig().openJDKLocation().pathAppended("bin");
|
2019-11-14 16:07:55 +01:00
|
|
|
jdbPath = jdbPath.pathAppended(Utils::HostOsInfo::withExecutableSuffix("jdb"));
|
2018-03-17 09:31:56 +02:00
|
|
|
|
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());
|
2019-12-05 10:32:35 +01:00
|
|
|
qCDebug(androidRunWorkerLog) << "Starting JDB:" << CommandLine(jdbPath, jdbArgs).toUserOutput();
|
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()) {
|
2019-03-01 17:10:38 +01: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";
|
2020-04-15 09:01:38 +02:00
|
|
|
auto waitForCommand = [this]() {
|
2018-03-17 09:31:56 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-01 17:10:38 +01:00
|
|
|
emit remoteProcessFinished(tr("Cannot attach JDB to the running application."));
|
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));
|
2020-04-15 09:01:38 +02:00
|
|
|
// App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes.
|
2018-03-17 09:31:56 +02:00
|
|
|
m_adbLogcatProcess.reset();
|
|
|
|
|
m_psIsAlive.reset();
|
|
|
|
|
m_jdbProcess.reset();
|
2020-04-15 04:33:06 +02:00
|
|
|
m_debugServerProcess.reset();
|
2018-03-17 09:31:56 +02:00
|
|
|
|
|
|
|
|
// 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.
|
2020-04-15 04:33:06 +02:00
|
|
|
emit remoteProcessStarted(m_localDebugServerPort, m_qmlServer, m_processPID);
|
2018-03-17 09:31:56 +02:00
|
|
|
logcatReadStandardOutput();
|
|
|
|
|
QTC_ASSERT(!m_psIsAlive, /**/);
|
2018-08-07 11:12:45 +02:00
|
|
|
QStringList isAliveArgs = selector() << "shell" << pidPollingScript.arg(m_processPID);
|
|
|
|
|
m_psIsAlive.reset(AndroidManager::runAdbCommandDetached(isAliveArgs));
|
|
|
|
|
QTC_ASSERT(m_psIsAlive, return);
|
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);
|
2019-02-26 17:48:18 +01:00
|
|
|
connect(m_psIsAlive.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
2018-05-09 12:20:54 +02:00
|
|
|
this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, -1));
|
2018-03-17 09:31:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Android
|