diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index cb2719e6a44..651338ab877 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -13,13 +13,15 @@ #include #include #include -#include #include #include +#include + #include #include +#include #include #include @@ -27,10 +29,9 @@ #include #include -#include +#include #include #include -#include #include #include @@ -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()) - == Debugger::LldbEngineType; + 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 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 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(); 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()->environment; + storage->m_extraEnvVars = runControl->aspectData()->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 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 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, - const SingleBarrier &settledBarrier) +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 storage; + const Storage 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 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; - const Storage 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 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 packageDirStorage; const Storage 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})); - }) || successItem; + 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", - "--listen", QString("*:%1").arg(s_localDebugServerPort.toString())})); + 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", - QString("+%1").arg(gdbServerSocket)})); + 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(), - Group { - parallel, - logcatRecipe(), - Group { - preStartRecipe(), - pidRecipe() + using PidUserPair = std::pair; + const Storage 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, + 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 { + 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" diff --git a/src/plugins/android/androidrunnerworker.h b/src/plugins/android/androidrunnerworker.h index 94674ba7472..2f4089fdb95 100644 --- a/src/plugins/android/androidrunnerworker.h +++ b/src/plugins/android/androidrunnerworker.h @@ -3,23 +3,19 @@ #pragma once -#include - -#include #include -#include -#include - -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 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 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