Android: Enclose internals inside a storage

Prepare for internal QThread removal.

Change-Id: I29f3a1d2a5e182eb305a95b9e810a079f16b7a2f
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2024-08-20 07:17:00 +02:00
parent b546c8524d
commit fca315aa76
2 changed files with 357 additions and 339 deletions

View File

@@ -13,13 +13,15 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/target.h>
#include <qmldebug/qmldebugcommandlinearguments.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitaspect.h>
#include <solutions/tasking/barrier.h>
#include <solutions/tasking/conditional.h>
#include <utils/hostosinfo.h>
@@ -27,10 +29,9 @@
#include <utils/qtcprocess.h>
#include <utils/url.h>
#include <QDate>
#include <QDateTime>
#include <QLoggingCategory>
#include <QRegularExpression>
#include <QScopeGuard>
#include <QTcpServer>
#include <chrono>
@@ -130,56 +131,108 @@ static FilePath debugServer(bool useLldb, const Target *target)
return {};
}
AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &deviceSerialNumber,
int apiLevel)
class RunnerStorage : public QObject
{
m_useLldb = Debugger::DebuggerKitAspect::engineType(runControl->kit())
Q_OBJECT
public:
QStringList selector() const { return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber); }
bool isPreNougat() const { return m_apiLevel > 0 && m_apiLevel <= 23; }
Utils::CommandLine adbCommand(std::initializer_list<Utils::CommandLine::ArgRef> args) const
{
CommandLine cmd{AndroidConfig::adbToolPath(), args};
cmd.prependArgs(selector());
return cmd;
}
QStringList userArgs() const
{
return m_processUser > 0 ? QStringList{"--user", QString::number(m_processUser)} : QStringList{};
}
QStringList packageArgs() const
{
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
return QStringList{"shell", "run-as", m_packageName} + userArgs();
}
QString m_packageName;
QString m_intentName;
QStringList m_beforeStartAdbCommands;
QStringList m_afterFinishAdbCommands;
QStringList m_amStartExtraArgs;
qint64 m_processPID = -1;
qint64 m_processUser = -1;
bool m_useCppDebugger = false;
bool m_useLldb = false;
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
QUrl m_qmlServer;
QString m_deviceSerialNumber;
int m_apiLevel = -1;
QString m_extraAppParams;
Utils::Environment m_extraEnvVars;
Utils::FilePath m_debugServerPath; // On build device, typically as part of ndk
bool m_useAppParamsForQmlDebugger = false;
signals:
void remoteProcessStarted(Utils::Port debugServerPort, const QUrl &qmlServer, qint64 pid);
void remoteProcessFinished(const QString &errString = QString());
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
void cancel();
};
static void setupStorage(RunnerStorage *storage, RunControl *runControl,
const QString &deviceSerialNumber, int apiLevel)
{
storage->m_useLldb = Debugger::DebuggerKitAspect::engineType(runControl->kit())
== Debugger::LldbEngineType;
auto aspect = runControl->aspectData<Debugger::DebuggerRunConfigurationAspect>();
const Id runMode = runControl->runMode();
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
m_useCppDebugger = debuggingMode && aspect->useCppDebugger;
storage->m_useCppDebugger = debuggingMode && aspect->useCppDebugger;
if (debuggingMode && aspect->useQmlDebugger)
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
storage->m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
storage->m_qmlDebugServices = QmlDebug::QmlProfilerServices;
else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlPreviewServices;
storage->m_qmlDebugServices = QmlDebug::QmlPreviewServices;
else
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
storage->m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
if (storage->m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
qCDebug(androidRunWorkerLog) << "QML debugging enabled";
QTcpServer server;
const bool isListening = server.listen(QHostAddress::LocalHost);
QTC_ASSERT(isListening,
qDebug() << Tr::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());
qCDebug(androidRunWorkerLog) << "QML server:" << m_qmlServer.toDisplayString();
storage->m_qmlServer.setScheme(Utils::urlTcpScheme());
storage->m_qmlServer.setHost(server.serverAddress().toString());
storage->m_qmlServer.setPort(server.serverPort());
qCDebug(androidRunWorkerLog) << "QML server:" << storage->m_qmlServer.toDisplayString();
}
auto target = runControl->target();
m_packageName = AndroidManager::packageName(target);
m_intentName = m_packageName + '/' + AndroidManager::activityName(target);
m_deviceSerialNumber = deviceSerialNumber;
m_apiLevel = apiLevel;
qCDebug(androidRunWorkerLog) << "Intent name:" << m_intentName
<< "Package name:" << m_packageName;
qCDebug(androidRunWorkerLog) << "Device API:" << m_apiLevel;
storage->m_packageName = AndroidManager::packageName(target);
storage->m_intentName = storage->m_packageName + '/' + AndroidManager::activityName(target);
storage->m_deviceSerialNumber = deviceSerialNumber;
storage->m_apiLevel = apiLevel;
qCDebug(androidRunWorkerLog) << "Intent name:" << storage->m_intentName
<< "Package name:" << storage->m_packageName;
qCDebug(androidRunWorkerLog) << "Device API:" << storage->m_apiLevel;
m_extraEnvVars = runControl->aspectData<EnvironmentAspect>()->environment;
storage->m_extraEnvVars = runControl->aspectData<EnvironmentAspect>()->environment;
qCDebug(androidRunWorkerLog).noquote() << "Environment variables for the app"
<< m_extraEnvVars.toStringList();
<< storage->m_extraEnvVars.toStringList();
if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release)
m_extraAppParams = runControl->commandLine().arguments();
storage->m_extraAppParams = runControl->commandLine().arguments();
if (const Store sd = runControl->settingsData(Constants::ANDROID_AM_START_ARGS);
!sd.isEmpty()) {
QTC_CHECK(sd.first().typeId() == QMetaType::QString);
const QString startArgs = sd.first().toString();
m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix);
storage->m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix);
}
if (const Store sd = runControl->settingsData(Constants::ANDROID_PRESTARTSHELLCMDLIST);
@@ -188,7 +241,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &
QTC_CHECK(first.typeId() == QMetaType::QStringList);
const QStringList commands = first.toStringList();
for (const QString &shellCmd : commands)
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
storage->m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
}
if (const Store sd = runControl->settingsData(Constants::ANDROID_POSTFINISHSHELLCMDLIST);
@@ -197,76 +250,67 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &
QTC_CHECK(first.typeId() == QMetaType::QStringList);
const QStringList commands = first.toStringList();
for (const QString &shellCmd : commands)
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
storage->m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
}
m_debugServerPath = debugServer(m_useLldb, target);
qCDebug(androidRunWorkerLog).noquote() << "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
<< ", Debug server path:" << m_debugServerPath;
storage->m_debugServerPath = debugServer(storage->m_useLldb, target);
qCDebug(androidRunWorkerLog).noquote() << "Device Serial:" << storage->m_deviceSerialNumber
<< ", API level:" << storage->m_apiLevel
<< ", Extra Start Args:" << storage->m_amStartExtraArgs
<< ", Before Start ADB cmds:" << storage->m_beforeStartAdbCommands
<< ", After finish ADB cmds:" << storage->m_afterFinishAdbCommands
<< ", Debug server path:" << storage->m_debugServerPath;
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit());
m_useAppParamsForQmlDebugger = version->qtVersion() >= QVersionNumber(5, 12);
storage->m_useAppParamsForQmlDebugger = version->qtVersion() >= QVersionNumber(5, 12);
}
AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &deviceSerialNumber,
int apiLevel)
: m_storage(new RunnerStorage)
{
m_storage->setParent(this); // Move m_storage object together with *this into a separate thread.
setupStorage(m_storage.get(), runControl, deviceSerialNumber, apiLevel);
m_taskTreeRunner.setParent(this); // Move m_taskTreeRunner object together with *this into a separate thread.
connect(m_storage.get(), &RunnerStorage::remoteProcessStarted,
this, &AndroidRunnerWorker::remoteProcessStarted);
connect(m_storage.get(), &RunnerStorage::remoteProcessFinished,
this, &AndroidRunnerWorker::remoteProcessFinished);
connect(m_storage.get(), &RunnerStorage::remoteOutput,
this, &AndroidRunnerWorker::remoteOutput);
connect(m_storage.get(), &RunnerStorage::remoteErrorOutput,
this, &AndroidRunnerWorker::remoteErrorOutput);
connect(this, &AndroidRunnerWorker::cancel, m_storage.get(), &RunnerStorage::cancel);
}
AndroidRunnerWorker::~AndroidRunnerWorker()
static ExecutableItem forceStopRecipe(RunnerStorage *storage)
{
if (m_processPID != -1)
TaskTree::runBlocking(Group { forceStopRecipe(), postDoneRecipe() });
}
QStringList AndroidRunnerWorker::selector() const
{
return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber);
}
CommandLine AndroidRunnerWorker::adbCommand(std::initializer_list<CommandLine::ArgRef> args) const
{
CommandLine cmd{AndroidConfig::adbToolPath(), args};
cmd.prependArgs(selector());
return cmd;
}
QStringList AndroidRunnerWorker::userArgs() const
{
return m_processUser > 0 ? QStringList{"--user", QString::number(m_processUser)} : QStringList{};
}
QStringList AndroidRunnerWorker::packageArgs() const
{
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
return QStringList{"shell", "run-as", m_packageName} + userArgs();
}
ExecutableItem AndroidRunnerWorker::forceStopRecipe()
{
const auto onForceStopSetup = [this](Process &process) {
process.setCommand(adbCommand({"shell", "am", "force-stop", m_packageName}));
const auto onForceStopSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "am", "force-stop", storage->m_packageName}));
};
const auto pidCheckSync = Sync([this] { return m_processPID != -1; });
const auto pidCheckSync = Sync([storage] { return storage->m_processPID != -1; });
const auto onPidOfSetup = [this](Process &process) {
process.setCommand(adbCommand({"shell", "pidof", m_packageName}));
const auto onPidOfSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "pidof", storage->m_packageName}));
};
const auto onPidOfDone = [this](const Process &process) {
const auto onPidOfDone = [storage](const Process &process) {
const QString pid = process.cleanedStdOut().trimmed();
return pid == QString::number(m_processPID);
return pid == QString::number(storage->m_processPID);
};
const auto pidOfTask = ProcessTask(onPidOfSetup, onPidOfDone, CallDoneIf::Success);
const auto onRunAsSetup = [this](Process &process) {
process.setCommand(adbCommand({"shell", "run-as", m_packageName, "kill", "-9",
QString::number(m_processPID)}));
const auto onRunAsSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "run-as", storage->m_packageName, "kill", "-9",
QString::number(storage->m_processPID)}));
};
const auto runAsTask = ProcessTask(onRunAsSetup);
const auto onKillSetup = [this](Process &process) {
process.setCommand(adbCommand({"shell", "kill", "-9", QString::number(m_processPID)}));
const auto onKillSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "kill", "-9",
QString::number(storage->m_processPID)}));
};
return Group {
@@ -277,8 +321,8 @@ ExecutableItem AndroidRunnerWorker::forceStopRecipe()
};
}
ExecutableItem AndroidRunnerWorker::removeForwardPortRecipe(
const QString &port, const QString &adbArg, const QString &portType)
static ExecutableItem removeForwardPortRecipe(RunnerStorage *storage, const QString &port,
const QString &adbArg, const QString &portType)
{
const auto onForwardListSetup = [](Process &process) {
process.setCommand({AndroidConfig::adbToolPath(), {"forward", "--list"}});
@@ -287,22 +331,22 @@ ExecutableItem AndroidRunnerWorker::removeForwardPortRecipe(
return process.cleanedStdOut().trimmed().contains(port);
};
const auto onForwardRemoveSetup = [this, port](Process &process) {
process.setCommand(adbCommand({"--remove", port}));
const auto onForwardRemoveSetup = [storage, port](Process &process) {
process.setCommand(storage->adbCommand({"--remove", port}));
};
const auto onForwardRemoveDone = [this](const Process &process) {
emit remoteErrorOutput(process.cleanedStdErr().trimmed());
const auto onForwardRemoveDone = [storage](const Process &process) {
emit storage->remoteErrorOutput(process.cleanedStdErr().trimmed());
return true;
};
const auto onForwardPortSetup = [this, port, adbArg](Process &process) {
process.setCommand(adbCommand({"forward", port, adbArg}));
const auto onForwardPortSetup = [storage, port, adbArg](Process &process) {
process.setCommand(storage->adbCommand({"forward", port, adbArg}));
};
const auto onForwardPortDone = [this, port, portType](DoneWith result) {
const auto onForwardPortDone = [storage, port, portType](DoneWith result) {
if (result == DoneWith::Success)
m_afterFinishAdbCommands.push_back("forward --remove " + port);
storage->m_afterFinishAdbCommands.push_back("forward --remove " + port);
else
emit remoteProcessFinished(Tr::tr("Failed to forward %1 debugging ports.").arg(portType));
emit storage->remoteProcessFinished(Tr::tr("Failed to forward %1 debugging ports.").arg(portType));
};
return Group {
@@ -315,17 +359,17 @@ ExecutableItem AndroidRunnerWorker::removeForwardPortRecipe(
// The startBarrier is passed when logcat process received "Sending WAIT chunk" message.
// The settledBarrier is passed when logcat process received "debugger has settled" message.
ExecutableItem AndroidRunnerWorker::jdbRecipe(const SingleBarrier &startBarrier,
static ExecutableItem jdbRecipe(RunnerStorage *storage, const SingleBarrier &startBarrier,
const SingleBarrier &settledBarrier)
{
const auto onSetup = [this] {
return m_useCppDebugger ? SetupResult::Continue : SetupResult::StopWithSuccess;
const auto onSetup = [storage] {
return storage->m_useCppDebugger ? SetupResult::Continue : SetupResult::StopWithSuccess;
};
const auto onTaskTreeSetup = [this](TaskTree &taskTree) {
const auto onTaskTreeSetup = [storage](TaskTree &taskTree) {
taskTree.setRecipe({
removeForwardPortRecipe("tcp:" + s_localJdbServerPort.toString(),
"jdwp:" + QString::number(m_processPID), "JDB")
removeForwardPortRecipe(storage, "tcp:" + s_localJdbServerPort.toString(),
"jdwp:" + QString::number(storage->m_processPID), "JDB")
});
};
@@ -338,7 +382,7 @@ ExecutableItem AndroidRunnerWorker::jdbRecipe(const SingleBarrier &startBarrier,
process.setProcessMode(ProcessMode::Writer);
process.setProcessChannelMode(QProcess::MergedChannels);
process.setReaperTimeout(s_jdbTimeout);
connect(settledBarrier->barrier(), &Barrier::done, &process, [processPtr = &process] {
QObject::connect(settledBarrier->barrier(), &Barrier::done, &process, [processPtr = &process] {
processPtr->write("ignore uncaught java.lang.Throwable\n"
"threads\n"
"cont\n"
@@ -359,7 +403,7 @@ ExecutableItem AndroidRunnerWorker::jdbRecipe(const SingleBarrier &startBarrier,
};
}
ExecutableItem AndroidRunnerWorker::logcatRecipe()
static ExecutableItem logcatRecipe(RunnerStorage *storage)
{
struct Buffer {
QStringList timeArgs;
@@ -367,24 +411,24 @@ ExecutableItem AndroidRunnerWorker::logcatRecipe()
QByteArray stdErrBuffer;
};
const Storage<Buffer> storage;
const Storage<Buffer> bufferStorage;
const SingleBarrier startJdbBarrier; // When logcat received "Sending WAIT chunk".
const SingleBarrier settledJdbBarrier; // When logcat received "debugger has settled".
const auto onTimeSetup = [this](Process &process) {
process.setCommand(adbCommand({"shell", "date", "+%s"}));
const auto onTimeSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "date", "+%s"}));
};
const auto onTimeDone = [storage](const Process &process) {
storage->timeArgs = {"-T", QDateTime::fromSecsSinceEpoch(
const auto onTimeDone = [bufferStorage](const Process &process) {
bufferStorage->timeArgs = {"-T", QDateTime::fromSecsSinceEpoch(
process.cleanedStdOut().trimmed().toInt()).toString("MM-dd hh:mm:ss.mmm")};
};
const auto onLogcatSetup = [this, storage, startJdbBarrier, settledJdbBarrier](Process &process) {
Buffer *bufferPtr = storage.activeStorage();
const auto parseLogcat = [this, bufferPtr, start = startJdbBarrier->barrier(),
const auto onLogcatSetup = [storage, bufferStorage, startJdbBarrier, settledJdbBarrier](Process &process) {
Buffer *bufferPtr = bufferStorage.activeStorage();
const auto parseLogcat = [storage, bufferPtr, start = startJdbBarrier->barrier(),
settled = settledJdbBarrier->barrier(), processPtr = &process](
QProcess::ProcessChannel channel) {
if (m_processPID == -1)
if (storage->m_processPID == -1)
return;
QByteArray &buffer = channel == QProcess::StandardOutput ? bufferPtr->stdOutBuffer
@@ -400,13 +444,13 @@ ExecutableItem AndroidRunnerWorker::logcatRecipe()
else
buffer = lines.takeLast(); // incomplete line
const QString pidString = QString::number(m_processPID);
const QString pidString = QString::number(storage->m_processPID);
for (const QByteArray &msg : std::as_const(lines)) {
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
if (!line.contains(pidString))
continue;
if (m_useCppDebugger) {
if (storage->m_useCppDebugger) {
if (start->current() == 0 && msg.trimmed().endsWith("Sending WAIT chunk"))
start->advance();
else if (settled->current() == 0 && msg.indexOf("debugger has settled") > 0)
@@ -437,27 +481,27 @@ ExecutableItem AndroidRunnerWorker::logcatRecipe()
const QString msgType = match.captured(2);
const QString output = line.mid(match.capturedStart(2));
if (onlyError || msgType == "F" || msgType == "E" || msgType == "W")
emit remoteErrorOutput(output);
emit storage->remoteErrorOutput(output);
else
emit remoteOutput(output);
emit storage->remoteOutput(output);
}
} else {
if (onlyError || line.startsWith("F/") || line.startsWith("E/")
|| line.startsWith("W/")) {
emit remoteErrorOutput(line);
emit storage->remoteErrorOutput(line);
} else {
emit remoteOutput(line);
emit storage->remoteOutput(line);
}
}
}
};
connect(&process, &Process::readyReadStandardOutput, this, [parseLogcat] {
QObject::connect(&process, &Process::readyReadStandardOutput, &process, [parseLogcat] {
parseLogcat(QProcess::StandardOutput);
});
connect(&process, &Process::readyReadStandardError, this, [parseLogcat] {
QObject::connect(&process, &Process::readyReadStandardError, &process, [parseLogcat] {
parseLogcat(QProcess::StandardError);
});
process.setCommand(adbCommand({"logcat", storage->timeArgs}));
process.setCommand(storage->adbCommand({"logcat", bufferStorage->timeArgs}));
};
return Group {
@@ -465,73 +509,73 @@ ExecutableItem AndroidRunnerWorker::logcatRecipe()
startJdbBarrier,
settledJdbBarrier,
Group {
storage,
bufferStorage,
ProcessTask(onTimeSetup, onTimeDone, CallDoneIf::Success) || successItem,
ProcessTask(onLogcatSetup)
},
jdbRecipe(startJdbBarrier, settledJdbBarrier)
jdbRecipe(storage, startJdbBarrier, settledJdbBarrier)
};
}
ExecutableItem AndroidRunnerWorker::preStartRecipe()
static ExecutableItem preStartRecipe(RunnerStorage *storage)
{
const QString port = "tcp:" + QString::number(m_qmlServer.port());
const QString port = "tcp:" + QString::number(storage->m_qmlServer.port());
const Storage<QStringList> argsStorage;
const LoopList iterator(m_beforeStartAdbCommands);
const LoopList iterator(storage->m_beforeStartAdbCommands);
const auto onArgsSetup = [this, argsStorage] {
*argsStorage = {"shell", "am", "start", "-n", m_intentName};
if (m_useCppDebugger)
const auto onArgsSetup = [storage, argsStorage] {
*argsStorage = {"shell", "am", "start", "-n", storage->m_intentName};
if (storage->m_useCppDebugger)
*argsStorage << "-D";
};
const auto onPreCommandSetup = [this, iterator](Process &process) {
process.setCommand(adbCommand({iterator->split(' ', Qt::SkipEmptyParts)}));
const auto onPreCommandSetup = [storage, iterator](Process &process) {
process.setCommand(storage->adbCommand({iterator->split(' ', Qt::SkipEmptyParts)}));
};
const auto onPreCommandDone = [this](const Process &process) {
emit remoteErrorOutput(process.cleanedStdErr().trimmed());
const auto onPreCommandDone = [storage](const Process &process) {
emit storage->remoteErrorOutput(process.cleanedStdErr().trimmed());
};
const auto onQmlDebugSetup = [this] {
return m_qmlDebugServices == QmlDebug::NoQmlDebugServices ? SetupResult::StopWithSuccess
const auto onQmlDebugSetup = [storage] {
return storage->m_qmlDebugServices == QmlDebug::NoQmlDebugServices ? SetupResult::StopWithSuccess
: SetupResult::Continue;
};
const auto onQmlDebugDone = [this, argsStorage] {
const auto onQmlDebugDone = [storage, argsStorage] {
const QString qmljsdebugger = QString("port:%1,block,services:%2")
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
.arg(storage->m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(storage->m_qmlDebugServices));
if (m_useAppParamsForQmlDebugger) {
if (!m_extraAppParams.isEmpty())
m_extraAppParams.prepend(' ');
m_extraAppParams.prepend("-qmljsdebugger=" + qmljsdebugger);
if (storage->m_useAppParamsForQmlDebugger) {
if (!storage->m_extraAppParams.isEmpty())
storage->m_extraAppParams.prepend(' ');
storage->m_extraAppParams.prepend("-qmljsdebugger=" + qmljsdebugger);
} else {
*argsStorage << "-e" << "qml_debug" << "true"
<< "-e" << "qmljsdebugger" << qmljsdebugger;
}
};
const auto onActivitySetup = [this, argsStorage](Process &process) {
const auto onActivitySetup = [storage, argsStorage](Process &process) {
QStringList args = *argsStorage;
args << m_amStartExtraArgs;
args << storage->m_amStartExtraArgs;
if (!m_extraAppParams.isEmpty()) {
if (!storage->m_extraAppParams.isEmpty()) {
const QStringList appArgs =
ProcessArgs::splitArgs(m_extraAppParams, Utils::OsType::OsTypeLinux);
ProcessArgs::splitArgs(storage->m_extraAppParams, Utils::OsType::OsTypeLinux);
qCDebug(androidRunWorkerLog).noquote() << "Using application arguments: " << appArgs;
args << "-e" << "extraappparams"
<< QString::fromLatin1(appArgs.join(' ').toUtf8().toBase64());
}
if (m_extraEnvVars.hasChanges()) {
if (storage->m_extraEnvVars.hasChanges()) {
args << "-e" << "extraenvvars"
<< QString::fromLatin1(m_extraEnvVars.toStringList().join('\t')
<< QString::fromLatin1(storage->m_extraEnvVars.toStringList().join('\t')
.toUtf8().toBase64());
}
process.setCommand(adbCommand({args}));
process.setCommand(storage->adbCommand({args}));
};
const auto onActivityDone = [this](const Process &process) {
emit remoteProcessFinished(Tr::tr("Activity Manager error: %1")
const auto onActivityDone = [storage](const Process &process) {
emit storage->remoteProcessFinished(Tr::tr("Activity Manager error: %1")
.arg(process.cleanedStdErr().trimmed()));
};
@@ -544,28 +588,28 @@ ExecutableItem AndroidRunnerWorker::preStartRecipe()
},
Group {
onGroupSetup(onQmlDebugSetup),
removeForwardPortRecipe(port, port, "QML"),
removeForwardPortRecipe(storage, port, port, "QML"),
onGroupDone(onQmlDebugDone, CallDoneIf::Success)
},
ProcessTask(onActivitySetup, onActivityDone, CallDoneIf::Error)
};
}
ExecutableItem AndroidRunnerWorker::postDoneRecipe()
static ExecutableItem postDoneRecipe(RunnerStorage *storage)
{
const LoopUntil iterator([this](int iteration) {
return iteration < m_afterFinishAdbCommands.size();
const LoopUntil iterator([storage](int iteration) {
return iteration < storage->m_afterFinishAdbCommands.size();
});
const auto onProcessSetup = [this, iterator](Process &process) {
process.setCommand(adbCommand(
{m_afterFinishAdbCommands.at(iterator.iteration()).split(' ', Qt::SkipEmptyParts)}));
const auto onProcessSetup = [storage, iterator](Process &process) {
process.setCommand(storage->adbCommand(
{storage->m_afterFinishAdbCommands.at(iterator.iteration()).split(' ', Qt::SkipEmptyParts)}));
};
const auto onDone = [this] {
m_processPID = -1;
m_processUser = -1;
emit remoteProcessFinished("\n\n" + Tr::tr("\"%1\" died.").arg(m_packageName));
const auto onDone = [storage] {
storage->m_processPID = -1;
storage->m_processUser = -1;
emit storage->remoteProcessFinished("\n\n" + Tr::tr("\"%1\" died.").arg(storage->m_packageName));
};
return Group {
@@ -578,97 +622,21 @@ ExecutableItem AndroidRunnerWorker::postDoneRecipe()
};
}
ExecutableItem AndroidRunnerWorker::pidRecipe()
{
using PidUserPair = std::pair<qint64, qint64>;
const Storage<PidUserPair> pidStorage;
const QString pidScript = isPreNougat()
? QString("for p in /proc/[0-9]*; do cat <$p/cmdline && echo :${p##*/}; done")
: QString("pidof -s '%1'").arg(m_packageName);
const auto onPidSetup = [this, pidScript](Process &process) {
process.setCommand(adbCommand({"shell", pidScript}));
};
const auto onPidDone = [pidStorage, packageName = m_packageName,
isPreNougat = isPreNougat()](const Process &process) {
const QString out = process.allOutput();
if (isPreNougat)
pidStorage->first = extractPID(out, packageName);
else if (!out.isEmpty())
pidStorage->first = out.trimmed().toLongLong();
};
const auto onUserSetup = [this, pidStorage](Process &process) {
process.setCommand(
adbCommand({"shell", "ps", "-o", "user", "-p", QString::number(pidStorage->first)}));
};
const auto onUserDone = [pidStorage](const Process &process) {
const QString out = process.allOutput();
if (out.isEmpty())
return DoneResult::Error;
QRegularExpressionMatch match;
qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
if (matchPos >= 0 && match.capturedLength(1) > 0) {
bool ok = false;
const qint64 processUser = match.captured(1).toInt(&ok);
if (ok) {
pidStorage->second = processUser;
return DoneResult::Success;
}
}
return DoneResult::Error;
};
const auto onPidSync = [this, pidStorage] {
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pidStorage->first;
m_processPID = pidStorage->first;
m_processUser = pidStorage->second;
emit remoteProcessStarted(s_localDebugServerPort, m_qmlServer, m_processPID);
};
const auto onIsAliveSetup = [this, pidStorage](Process &process) {
process.setProcessChannelMode(QProcess::MergedChannels);
process.setCommand(adbCommand({"shell", pidPollingScript.arg(pidStorage->first)}));
};
return Group {
pidStorage,
onGroupSetup([pidStorage] { *pidStorage = {-1, 0}; }),
Forever {
stopOnSuccess,
ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 200ms; },
DoneResult::Error)
}.withTimeout(45s),
ProcessTask(onUserSetup, onUserDone, CallDoneIf::Success),
Sync(onPidSync),
Group {
parallel,
startNativeDebuggingRecipe(),
ProcessTask(onIsAliveSetup)
},
postDoneRecipe()
};
}
static QString tempDebugServerPath(int count)
{
static const QString tempDebugServerPathTemplate = "/data/local/tmp/%1";
return tempDebugServerPathTemplate.arg(count);
}
ExecutableItem AndroidRunnerWorker::uploadDebugServerRecipe(const QString &debugServerFileName)
static ExecutableItem uploadDebugServerRecipe(RunnerStorage *storage, const QString &debugServerFileName)
{
const Storage<QString> tempDebugServerPathStorage;
const LoopUntil iterator([tempDebugServerPathStorage](int iteration) {
return tempDebugServerPathStorage->isEmpty() && iteration <= GdbTempFileMaxCounter;
});
const auto onDeviceFileExistsSetup = [this, iterator](Process &process) {
const auto onDeviceFileExistsSetup = [storage, iterator](Process &process) {
process.setCommand(
adbCommand({"shell", "ls", tempDebugServerPath(iterator.iteration()), "2>/dev/null"}));
storage->adbCommand({"shell", "ls", tempDebugServerPath(iterator.iteration()), "2>/dev/null"}));
};
const auto onDeviceFileExistsDone = [iterator, tempDebugServerPathStorage](
const Process &process, DoneWith result) {
@@ -683,25 +651,25 @@ ExecutableItem AndroidRunnerWorker::uploadDebugServerRecipe(const QString &debug
return tempDirOK;
};
const auto onCleanupSetup = [this, tempDebugServerPathStorage](Process &process) {
process.setCommand(adbCommand({"shell", "rm", "-f", *tempDebugServerPathStorage}));
const auto onCleanupSetup = [storage, tempDebugServerPathStorage](Process &process) {
process.setCommand(storage->adbCommand({"shell", "rm", "-f", *tempDebugServerPathStorage}));
};
const auto onCleanupDone = [] {
qCDebug(androidRunWorkerLog) << "Debug server cleanup failed.";
};
const auto onServerUploadSetup = [this, tempDebugServerPathStorage](Process &process) {
const auto onServerUploadSetup = [storage, tempDebugServerPathStorage](Process &process) {
process.setCommand(
adbCommand({"push", m_debugServerPath.toString(), *tempDebugServerPathStorage}));
storage->adbCommand({"push", storage->m_debugServerPath.toString(), *tempDebugServerPathStorage}));
};
const auto onServerCopySetup = [this, tempDebugServerPathStorage, debugServerFileName](Process &process) {
process.setCommand(adbCommand({packageArgs(), "cp", *tempDebugServerPathStorage,
debugServerFileName}));
const auto onServerCopySetup = [storage, tempDebugServerPathStorage, debugServerFileName](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "cp",
*tempDebugServerPathStorage, debugServerFileName}));
};
const auto onServerChmodSetup = [this, debugServerFileName](Process &process) {
process.setCommand(adbCommand({packageArgs(), "chmod", "777", debugServerFileName}));
const auto onServerChmodSetup = [storage, debugServerFileName](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "chmod", "777", debugServerFileName}));
};
return Group {
@@ -724,64 +692,64 @@ ExecutableItem AndroidRunnerWorker::uploadDebugServerRecipe(const QString &debug
};
}
ExecutableItem AndroidRunnerWorker::startNativeDebuggingRecipe()
static ExecutableItem startNativeDebuggingRecipe(RunnerStorage *storage)
{
const auto onSetup = [this] {
return m_useCppDebugger ? SetupResult::Continue : SetupResult::StopWithSuccess;
const auto onSetup = [storage] {
return storage->m_useCppDebugger ? SetupResult::Continue : SetupResult::StopWithSuccess;
};
const Storage<QString> packageDirStorage;
const Storage<QString> debugServerFileStorage;
const auto onAppDirSetup = [this](Process &process) {
process.setCommand(adbCommand({packageArgs(), "/system/bin/sh", "-c", "pwd"}));
const auto onAppDirSetup = [storage](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "/system/bin/sh", "-c", "pwd"}));
};
const auto onAppDirDone = [this, packageDirStorage](const Process &process, DoneWith result) {
const auto onAppDirDone = [storage, packageDirStorage](const Process &process, DoneWith result) {
if (result == DoneWith::Success)
*packageDirStorage = process.stdOut();
else
emit remoteProcessFinished(Tr::tr("Failed to find application directory."));
emit storage->remoteProcessFinished(Tr::tr("Failed to find application directory."));
};
// Add executable flag to package dir. Gdb can't connect to running server on device on
// e.g. on Android 8 with NDK 10e
const auto onChmodSetup = [this, packageDirStorage](Process &process) {
process.setCommand(adbCommand({packageArgs(), "chmod", "a+x", packageDirStorage->trimmed()}));
const auto onChmodSetup = [storage, packageDirStorage](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "chmod", "a+x", packageDirStorage->trimmed()}));
};
const auto onServerPathCheck = [this] {
if (m_debugServerPath.exists())
const auto onServerPathCheck = [storage] {
if (storage->m_debugServerPath.exists())
return true;
QString msg = Tr::tr("Cannot find C++ debug server in NDK installation.");
if (m_useLldb)
if (storage->m_useLldb)
msg += "\n" + Tr::tr("The lldb-server binary has not been found.");
emit remoteProcessFinished(msg);
emit storage->remoteProcessFinished(msg);
return false;
};
const auto useLldb = Sync([this] { return m_useLldb; });
const auto killAll = [this](const QString &name) {
return ProcessTask([this, name](Process &process) {
process.setCommand(adbCommand({packageArgs(), "killall", name}));
const auto useLldb = Sync([storage] { return storage->m_useLldb; });
const auto killAll = [storage](const QString &name) {
return ProcessTask([storage, name](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "killall", name}));
}) || successItem;
};
const auto setDebugServer = [debugServerFileStorage](const QString &fileName) {
return Sync([debugServerFileStorage, fileName] { *debugServerFileStorage = fileName; });
};
const auto uploadDebugServer = [this, setDebugServer](const QString &debugServerFileName) {
return If (uploadDebugServerRecipe(debugServerFileName)) >> Then {
const auto uploadDebugServer = [storage, setDebugServer](const QString &debugServerFileName) {
return If (uploadDebugServerRecipe(storage, debugServerFileName)) >> Then {
setDebugServer(debugServerFileName)
} >> Else {
Sync([this] {
emit remoteProcessFinished(Tr::tr("Cannot copy C++ debug server."));
Sync([storage] {
emit storage->remoteProcessFinished(Tr::tr("Cannot copy C++ debug server."));
return false;
})
};
};
const auto packageFileExists = [this](const QString &filePath) {
const auto onProcessSetup = [this, filePath](Process &process) {
process.setCommand(adbCommand({packageArgs(), "ls", filePath, "2>/dev/null"}));
const auto packageFileExists = [storage](const QString &filePath) {
const auto onProcessSetup = [storage, filePath](Process &process) {
process.setCommand(storage->adbCommand({storage->packageArgs(), "ls", filePath, "2>/dev/null"}));
};
const auto onProcessDone = [](const Process &process) {
return !process.stdOut().trimmed().isEmpty();
@@ -789,25 +757,25 @@ ExecutableItem AndroidRunnerWorker::startNativeDebuggingRecipe()
return ProcessTask(onProcessSetup, onProcessDone, CallDoneIf::Success);
};
const auto onRemoveGdbServerSetup = [this, packageDirStorage](Process &process) {
const auto onRemoveGdbServerSetup = [storage, packageDirStorage](Process &process) {
const QString gdbServerSocket = *packageDirStorage + "/debug-socket";
process.setCommand(adbCommand({packageArgs(), "rm", gdbServerSocket}));
process.setCommand(storage->adbCommand({storage->packageArgs(), "rm", gdbServerSocket}));
};
const auto onDebugServerSetup = [this, packageDirStorage, debugServerFileStorage](Process &process) {
if (m_useLldb) {
process.setCommand(adbCommand({packageArgs(), *debugServerFileStorage, "platform",
const auto onDebugServerSetup = [storage, packageDirStorage, debugServerFileStorage](Process &process) {
if (storage->m_useLldb) {
process.setCommand(storage->adbCommand({storage->packageArgs(), *debugServerFileStorage, "platform",
"--listen", QString("*:%1").arg(s_localDebugServerPort.toString())}));
} else {
const QString gdbServerSocket = *packageDirStorage + "/debug-socket";
process.setCommand(adbCommand({packageArgs(), *debugServerFileStorage, "--multi",
process.setCommand(storage->adbCommand({storage->packageArgs(), *debugServerFileStorage, "--multi",
QString("+%1").arg(gdbServerSocket)}));
}
};
const auto onTaskTreeSetup = [this, packageDirStorage](TaskTree &taskTree) {
const auto onTaskTreeSetup = [storage, packageDirStorage](TaskTree &taskTree) {
const QString gdbServerSocket = *packageDirStorage + "/debug-socket";
taskTree.setRecipe({removeForwardPortRecipe("tcp:" + s_localDebugServerPort.toString(),
taskTree.setRecipe({removeForwardPortRecipe(storage, "tcp:" + s_localDebugServerPort.toString(),
"localfilesystem:" + gdbServerSocket, "C++")});
};
@@ -844,28 +812,117 @@ ExecutableItem AndroidRunnerWorker::startNativeDebuggingRecipe()
};
}
void AndroidRunnerWorker::asyncStart()
static ExecutableItem pidRecipe(RunnerStorage *storage)
{
const Group recipe {
forceStopRecipe(),
using PidUserPair = std::pair<qint64, qint64>;
const Storage<PidUserPair> pidStorage;
const QString pidScript = storage->isPreNougat()
? QString("for p in /proc/[0-9]*; do cat <$p/cmdline && echo :${p##*/}; done")
: QString("pidof -s '%1'").arg(storage->m_packageName);
const auto onPidSetup = [storage, pidScript](Process &process) {
process.setCommand(storage->adbCommand({"shell", pidScript}));
};
const auto onPidDone = [pidStorage, packageName = storage->m_packageName,
isPreNougat = storage->isPreNougat()](const Process &process) {
const QString out = process.allOutput();
if (isPreNougat)
pidStorage->first = extractPID(out, packageName);
else if (!out.isEmpty())
pidStorage->first = out.trimmed().toLongLong();
};
const auto onUserSetup = [storage, pidStorage](Process &process) {
process.setCommand(
storage->adbCommand({"shell", "ps", "-o", "user", "-p", QString::number(pidStorage->first)}));
};
const auto onUserDone = [pidStorage](const Process &process) {
const QString out = process.allOutput();
if (out.isEmpty())
return DoneResult::Error;
QRegularExpressionMatch match;
qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
if (matchPos >= 0 && match.capturedLength(1) > 0) {
bool ok = false;
const qint64 processUser = match.captured(1).toInt(&ok);
if (ok) {
pidStorage->second = processUser;
return DoneResult::Success;
}
}
return DoneResult::Error;
};
const auto onPidSync = [storage, pidStorage] {
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << storage->m_processPID
<< "to:" << pidStorage->first;
storage->m_processPID = pidStorage->first;
storage->m_processUser = pidStorage->second;
emit storage->remoteProcessStarted(s_localDebugServerPort, storage->m_qmlServer, storage->m_processPID);
};
const auto onIsAliveSetup = [storage, pidStorage](Process &process) {
process.setProcessChannelMode(QProcess::MergedChannels);
process.setCommand(storage->adbCommand({"shell", pidPollingScript.arg(pidStorage->first)}));
};
return Group {
pidStorage,
onGroupSetup([pidStorage] { *pidStorage = {-1, 0}; }),
Forever {
stopOnSuccess,
ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 200ms; },
DoneResult::Error)
}.withTimeout(45s),
ProcessTask(onUserSetup, onUserDone, CallDoneIf::Success),
Sync(onPidSync),
Group {
parallel,
logcatRecipe(),
startNativeDebuggingRecipe(storage),
ProcessTask(onIsAliveSetup)
}
};
}
AndroidRunnerWorker::~AndroidRunnerWorker()
{
if (m_storage->m_processPID != -1)
TaskTree::runBlocking(Group { forceStopRecipe(m_storage.get()), postDoneRecipe(m_storage.get()) });
}
void AndroidRunnerWorker::asyncStart()
{
// TODO: Instead of asyncStop recipe, add a barrier storage.
const Group recipe {
finishAllAndSuccess,
Group {
preStartRecipe(),
pidRecipe()
forceStopRecipe(m_storage.get()),
Group {
parallel,
stopOnSuccessOrError,
logcatRecipe(m_storage.get()),
Group {
preStartRecipe(m_storage.get()),
pidRecipe(m_storage.get())
}
}
}.withCancel([storage = m_storage.get()] {
return std::make_pair(storage, &RunnerStorage::cancel);
}),
forceStopRecipe(m_storage.get()),
postDoneRecipe(m_storage.get())
};
m_taskTreeRunner.start(recipe);
}
void AndroidRunnerWorker::asyncStop()
{
if (m_processPID != -1)
m_taskTreeRunner.start(Group { forceStopRecipe(), postDoneRecipe() });
else
m_taskTreeRunner.reset();
emit cancel();
}
} // namespace Android::Internal
#include "androidrunnerworker.moc"

View File

@@ -3,23 +3,19 @@
#pragma once
#include <qmldebug/qmldebugcommandlinearguments.h>
#include <solutions/tasking/barrier.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/commandline.h>
#include <utils/environment.h>
namespace Android { class AndroidDeviceInfo; }
namespace ProjectExplorer { class RunControl; }
namespace Utils { class Port; }
namespace Android::Internal {
class RunnerStorage;
class AndroidRunnerWorker : public QObject
{
Q_OBJECT
public:
AndroidRunnerWorker(ProjectExplorer::RunControl *runControl, const QString &deviceSerialNumber,
int apiLevel);
@@ -35,46 +31,11 @@ signals:
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
void cancel();
private:
QStringList selector() const;
bool isPreNougat() const { return m_apiLevel > 0 && m_apiLevel <= 23; }
Utils::CommandLine adbCommand(std::initializer_list<Utils::CommandLine::ArgRef> args) const;
QStringList userArgs() const;
QStringList packageArgs() const;
Tasking::ExecutableItem forceStopRecipe();
Tasking::ExecutableItem removeForwardPortRecipe(const QString &port, const QString &adbArg,
const QString &portType);
Tasking::ExecutableItem jdbRecipe(const Tasking::SingleBarrier &startBarrier,
const Tasking::SingleBarrier &settledBarrier);
Tasking::ExecutableItem logcatRecipe();
Tasking::ExecutableItem preStartRecipe();
Tasking::ExecutableItem postDoneRecipe();
Tasking::ExecutableItem pidRecipe();
Tasking::ExecutableItem uploadDebugServerRecipe(const QString &debugServerFileName);
Tasking::ExecutableItem startNativeDebuggingRecipe();
// Create the processes and timer in the worker thread, for correct thread affinity
QString m_packageName;
QString m_intentName;
QStringList m_beforeStartAdbCommands;
QStringList m_afterFinishAdbCommands;
QStringList m_amStartExtraArgs;
qint64 m_processPID = -1;
qint64 m_processUser = -1;
std::unique_ptr<RunnerStorage> m_storage;
Tasking::TaskTreeRunner m_taskTreeRunner;
bool m_useCppDebugger = false;
bool m_useLldb = false; // FIXME: Un-implemented currently.
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
QUrl m_qmlServer;
QString m_deviceSerialNumber;
int m_apiLevel = -1;
QString m_extraAppParams;
Utils::Environment m_extraEnvVars;
Utils::FilePath m_debugServerPath; // On build device, typically as part of ndk
bool m_useAppParamsForQmlDebugger = false;
};
} // namespace Android::Internal